Variations Of The Strategy Pattern // Using Python features!

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
today i'm going to take a look at the strategy  pattern and how you can implement it in   python and i won't just look at the classic  object oriented version of it i'll also   introduce a couple of varieties built on  more recent features in the python language   including a really cool functional approach to  the strategy while i'm writing the code i'll be   using Tabnine an AI code completion assistant  that directly integrates into your IDE they're   also the sponsor of this video. Tabnine is an AI  assistant that provides smart code completion in   your IDT it supports over 30 languages including  python in 15 IDEs including vs code and pycharm   Tabnine offers both a local model and the cloud  model you can choose to run tap line locally   only and your code never leaves your machine this  also means you can use it to work offline while   ensuring maximum security and privacy Tabnine  recently launched Tabnine for teams which will   learn your teams projects preferences and patterns  suggesting even better code completions for you   and your team members you can get Tabnine  basic as a free extension to your IDE of choice   if you're a student you can get Tabnine pro free  for more information go to tabnine.com/students   or if you're not a student you can use coupon  code Arjan50 to get a discount of the pro plan   the links are in the description of this  video today's example is an example they had   in one of my earlier videos as well it's a  simple support ticket handling example this   main function that creates a customer support  instance i'll show you in a minute what that is   exactly then we're registering a few tickets and  then i'm calling a process tickets method that   gets a ticket processing strategy in this  case it's first in first out this relies on   customer support and support tickets support  ticket is a data class that's in this file it's   under the support folder so you can also see that  that's why i'm importing it like this here and   in this class it just has a few  fields it has customer it has an issue   and it has an id and what i'm doing here is that  the id is a field that is not initialized when you   create an instance of support ticket you can also  see this in the main function so i'm creating the   support ticket here but i'm not providing the  id the id is generated because in the post init   method that's the method that's called after the  object after the instance has been created i'm   calling a generate_id function and then storing  that generated id in the id field and this is a   very simple helper function that just uses random  uppercase characters and then generates a string   from that support ticket also has a process  function that does nothing at the moment it   just prints out the id customer and the issue  and customer support which is the other class   that the example is using it simply maintains  a list of tickets that's what you see here   it has an add ticket method and it has a process  ticket method that given this processing strategy   process the tickets in a different way  and the way i'm doing this here is using   an if else statements and depending on the  strategy i'm ordering the tickets differently   and then processing them once i'm  done processing these tickets i clear   the tickets list and that's basically all there  is to it let's run this code and see what it does let me just make this a bit larger so here  you see it's just processing these tickets   that i just added in the code here and this  is using first in first out i can change this   to another strategy and then we're going to  get a different result so now it's first in   last out and that means that this one that i  defined first is going to be processed last and   this is a typical example of where the strategy  pattern can help the strategy pattern allows you   to inject behavior into an application without  the code knowing exactly what the behavior does   here's a class diagram the pattern relies on  an interface basically the signature of the   strategy function to break up an application and  reduce coupling the idea is that the interface   specifies a method and then you create classes  that implement that interface and you can create   instances of those classes and inject them  into the different parts of your application   and then those parts of your program will  call that method without knowing what the   method is actually doing it's it only knows  the signature of the method that it calls so   let's look at the classic strategy pattern first  and then dive into a few really cool variations   the classic strategy pattern uses classes abstract  classes and inheritance to create the effect of   being able to insert different strategies and  that's a very typical classical object-oriented   way of approaching things later on i'll show  you a few other ways to do this but in order   to get started i just want to build the classic  thing and then we're going to modify it later   and hopefully also improve it a little bit so what  we're going to do in this case in order to remove   this if else statement here and use a strategy  pattern is to create for each of these different   processing strategies create a different strategy  subclass and actually it's not really a processing   strategy it's more on ordering strategy so what  i'm going to do is first first i'll import the abc abstract base class and abstract   method there we go and i'm going to create  a class called ticket ordering strategy so that's an abstract base class and  that also has one abstract method that's called create ordering and that gets a list of support tickets and that is also  going to return a list of support tickets there we go that's our basic ticket ordering  strategy class the only thing it does is create   an ordered list so now what we can do is  create subclasses of this abstract class so   let's say i'm going to create a first in first  ordering strategy and that's a sub class of the   ticket ordering strategy and that will have  a create ordering method that has the same   signature as the one in the abstract base class  and what this is going to do first in first out   it's just going to return a copy of the tickets  list we can create similar classes for each of the   different ordering strategies that we'd like to  have so let's also create first and last out one and what this is going to return is a reverse of  the tickets so i'm just going to call the reverse   function on the tickets list and then turning  that again into a list because reverse actually   is a generator and finally i'm going  to create the random ordering strategy   which of course also inherits from ticket ordering  strategy that also has a create ordering method   returns a list and what this  does is this returns a sample of the list of tickets of the length of the  tickets list so this is then going to return   a random ordering of the current list of tickers  that we have so these are our strategies then in   the customer support class we no longer provide  this strategy as a string but what we're going to   do is provide it as an instance of ticket ordering  strategy then the first thing we're going to do is create the ordered list let's call that ticket list and then  we're using the processing strategy   and we're calling create ordering with the  tickets that we currently have so that's our   ticket list and then the rest of the code  i'm going to work with ticket list instead   of the list of tickets that's the part of the  instance so here i'm going to write a ticket list   if there are no tickets don't do anything and  then this if else statement we can basically   replace by a very simple piece of code  so let me just remove this and i'll add a simple part that goes through  the list of tickets there we go so for each ticket we're going to  process it like so so now process tickets   no longer relies on any particular type  of processing strategy we could create   new ones and then use those instead obviously in  the main part i still need to update this because   here i have a process tickets function that  still passes a string i need to replace this   by passing a particular ticket processing  strategy so for example what i could do is   pass here a first in last out ordering strategy  like so it automatically imported that so that's   great let's run this and see that this still  works so yes this still works and what's   nice now because we have the strategy pattern is  that we can create our own custom strategies and   then use those instead without having to change  anything in the customer support class for example   i will write it here so you can see what the  difference is i could create a black hole strategy and that's a ticket ordering strategy   that's also automatically imported which is  nice and this needs a create ordering method just like the the other strategies and  that's going to get a list of tickets and that's also going to return a list of  support tickets and the black hole strategy   is very simple that just returns the empty  list so let me replace this so now i can just   write here black hole strategy and let's  run the example and you see there are now   no tickets to process which is great and  now this import here is no longer needed   there we go so the nice thing is we can create  new strategies like this one and i didn't have   to change anything in the customer support  class and that's the power of the classic   strategy pattern and note the dependencies here  so we're relying on customer support because we   create this instance of this class we're relying  on support ticket because we're creating tickets   and we rely on the ticket ordering strategy  because that's a super class of the black   hole strategy now instead of abstract base  classes and abstract methods you could also use   protocol classes so let's see how that works and  what the effect is of doing that so notice the   changes to using protocols i'm going back into the  app.pi file so we have here the ticket ordering   strategy which is currently an abstract base class  what i'm going to do instead is turn this into a   protocol and that's part of the typing library and  we don't need this abstract method thing anymore   because we're not going to use inheritance in this  case we're going to use the protocol mechanism   so i'll remove this here because that's not  needed anymore and because this is a protocol we   now don't need to create these subclasses but we  can just directly use these classes here and then   python's typing system python's duct typing system  is going to help us do this in a nice way so let's   delete the um the inheritance here and  i'm going to do the same thing here   and also the same thing here so these classes  no longer directly inherit from ticket ordering   strategy i don't need to change anything in  the customer support class because it's simply   expecting a processing strategy of this type so  that's all there is to it and now in the main part   because ticket ordering strategy is a protocol i  don't need to inherit here either so i can just   write the black hole strategy class like so and  that means i can also remove this dependency here   so you see what the effect is of using protocols  is that it basically removed one dependency one   extra import here because the duck typing system  is solving that for us we don't need to explicitly   inherit from this anymore i've discussed protocols  before of course there are some disadvantages to   using that for example i have to make sure  that this create ordering function actually   matches what is expected in the protocol and  if i make a mistake the the typing system might   not always pick that up automatically so with  abstract based classes this is a bit more strict   but still it's a nice alternative that you could  use for this so let's run this one more time and   we still have no tickets to process so that's  protocols another thing you can do is rely on   the dunder methods to simplify the code  even more let's go back into this app.py   file so as you can see actually the name of this  create ordering method doesn't really matter that   much because it's already kind of defined by  the name of the class so we're kind of doing a   double job here so what you could do  is instead of having a create ordering   method and simply having a call dunder method  and then you can just call the the the object   that you have and you're done so that even  shortens code a bit more so let's change this to   being a call dunder method like so and  then of course i also need to do that here   and here and he oh wait and here and then  in the customer support class i can simply   use processing strategy like this i'm kind  of using it like a function it's a callable   basically so this is even shorter and let me go  here to the main file because now obviously the   black hole strategy also needs to be updated  there we go and let's run this again to check   that this is still working yeah it is and just to  make sure i also want to try one of these other   classes let's say we're going to use the random  ordering strategy just want to make sure that this   still works correctly as well so now we're getting  our tickets back again in random order a couple of   weeks ago i did a video about the factory pattern  where i used a similar approach also using the   call dunder methods if you want to watch that  video i put a link here in the top so those are   a few varieties of the strategy pattern that still  kind of rely on object oriented programming using   classes in essence that's not really needed  because all that a strategy is is a function right   so why not simply use functions instead of  classes to simplify things even more when the   strategy pattern was conceived object oriented  programming languages were really strictly   object oriented everything is an object functions  did not exist there were only classes with methods   so there was no way to do it actually but of  course python has a lot of modern features it   blends aspects of functional programming with  object-oriented programming so there are other   ways to achieve the same thing but better and  not have to rely on these classes but doing   things in a more simple way so instead of having  classes either with protocols and inheritance or   with call dunder methods you could also simply  just define a bunch of functions and then those   functions are going to be the different strategies  that you use so i'm going into app.py and i'm   going to change these classes into functions now  what we do want to have is some way of defining   what a function should look like in order to  be used and that's basically the role that   ticket ordering strategy has in this case because  that's the protocol that you should follow but we   could actually simply define a type alias and then  use that type alias so i'll remove this class here   and i'm going to say that ticket ordering strategy  is a callable callable is part of the typing   library so i'll just import it here so this  is a callable and its input is going to be a   list of support tickets and the  result is also going to be a list of   support tickets so now we have our ticket  ordering strategy and what's interesting   is that now actually the program still works and  there are no typing issues so i can still run this   and this is all working fine even though i removed  the protocol class and replaced it by a simple   type and that's because the distinction  between classes objects and functions is   not as big as you might think they're basically  all callables and that means that if i specify   a type callable like so i could use an object of  a class with the call dunder method i could use a   function those things would work both just fine so  now for example what you can do is create another   function here called let's say first in first out  strategy and that's going to get a list of tickets like so and that is also going to  return a list of support tickets and let me just remove this like this so now  we have a function that does basically the same   thing as the class that we had before and i can  do the same thing for the first in last out one and then let me just remove this there we go  so now we have a first in last out strategy   and if i go back to the main part  of my code i can just put in here   the one of these functions and then process  tickets is going to use one of the functions   there we go first in first out and when i run this it uses that particular strategy so our type is  a callable type and it doesn't matter whether i'm   using a function which is a callable or whether  i'm using a class with a dunder method because an   instance of that class is also going to be a  callable so for example i can now still use here   the random ordering strategy and it will all just  work fine like so so this also works there's one   issue with using functions out even even though  there's a way around that as well and i want to   show you what i mean by that let's say you want  to have a strategy where you want to set some   parameters of the strategy for example this random  ordering strategy well maybe you want to set a   seed value before you process the tickets using  this strategy if you have a class like this you   would add an instance variable that would contain  the seed value and then you'd have an initializer   to to set that so let's add that here so i'm going  to turn random ordering strategy into a data class   and i'm going to provide it with one  seed value which let's say is an optional   because we don't always need a seed value so we  initialize it with none and then in the create   ordering what i'm going to do is set the seed  here random dot seed and then self dot seed   so now we're seeding the random number generator  with a particular value i'm not calling it with   that particular value so every time i run this  i'm gonna get a different result but i could now   pass it a seed value like so let's say i give it a  seed of one and now you see every time i run this   i'm going to get the same result because well the  seed is the same the problem occurs if i want to   turn this random ordering strategy into a regular  function because where do i set the seed i don't   have an initializer anymore and i can pass it  as an extra parameter to the strategy function   because the function is supposed to follow this  callable structure which only expects a list   of support tickets as an input i could make this  more generic by adding a dot dot dot or something   or adding arguments extra which i don't really  like that much because then basically customer   support needs to know about that stuff i did  another video about that a while ago i'll put a   link here in the top if you want to watch that but  basically that's the trade-off if you use these   simple functions then passing along parameters  is going to be a bit more complicated you can   still do it though and that's by using closures  so let's create a new function called random strategy creator and this takes the seed that by default is non and this will return a  ticket ordering strategy which is a callable so   this will return a function i'm going to define  this function inside the random strategy creator   function and this is the actual function that  follows the ticket ordering strategy type and there we're basically calling random.seed  with the seed and then returning the sample of the   tickets and the length of the tickets like so and  then we're going to return the random strategy and   now when i want to use this function i'm going to  go in here in the process ticket strategy and then   i'm going to call this random strategy creator and  that gets the seed it works exactly the same way   that a class would work and now you see that i can  simply run this i'm going to get the same result   if i remove this seed then we're going to get  the different behavior every time we run the   program this is called a closure because  basically we have variables like seeds that   are available inside the function and the  function is returned but if you look at the   main program actually this function is called and  then is passed as a parameter and that means that   this function or the closure keeps track  of the internal variables like seed   and that allows you to do a thing kind of similar  to how it also works with classes where you create   the instance that contains parameters  or instance variables that contains data   and then you have methods that can do something  with that data and here it's basically the same   thing so we also have a kind of a method but it's  called the closure that uses the seed parameter   and you could even define other local  variables here like for example i could   say i have a use seed something like this  right and then i could write here if you seed and then we're going to set set the seed something  like this and then you could even pass this as a   second parameter here if you wanted to and  these variables you define here inside this   function are actually a little bit more protected  than they would be in class if you have a class   with instance variables you can simply access  them by writing object dot instance variable   and here it's a bit more complicated they're not  true private variables you can still access them   but what you need to do is use a dunder method  to do it so when you do it it kind of is clear   that you're not supposed to do this so as you saw  in the example the distinction between functions   classes and objects is not as clear as you think  in fact classes objects with the call dunder   method and functions are all examples of callables  the syntax is just slightly different and each of   them offer different benefits the result is that  for most of these classic object-oriented patterns   you can probably mix classes functions and objects  differently to get variations of the pattern like   i did in the example code and in the end it's also  a matter of taste do you prefer simple functions   classes objects it's kind of up to you and in  software design it's much more important to   apply the design principles and the patterns and  the variations of the patterns follow from that   for example the strategy helps to reduce coupling  so the goal is to reduce coupling not so much   to use specifically the strategy pattern because  any of the variations that i discussed today also   reduce coupling and maybe even shorter and easier  to implement than the classic object-oriented   strategy pattern so i hope you enjoyed this if you  did give this video a like consider subscribing to   my channel if you want to watch more of my content  don't forget to check out Tabnine the sponsor of   this video links are in description below thanks  for watching take care and see you next week
Info
Channel: ArjanCodes
Views: 7,668
Rating: 4.980247 out of 5
Keywords: strategy pattern, design patterns, strategy design pattern, strategy pattern example, software design, strategy pattern examples, design patterns through python, strategy patterns, object oriented design, strategy pattern python, strategy pattern vs factory pattern, strategy pattern in design pattern, software engineering, software engineer, software development, headfirst design patterns, head first design patterns, python protocol, python closure, Startegy pattern
Id: n2b_Cxh20Fw
Channel Id: undefined
Length: 27min 20sec (1640 seconds)
Published: Fri Oct 01 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.