Strategy and Command Design Patterns — Wizards and Sandwiches — Applications in Python
It’s been a while since my last post … I’ve moved cities (Vancouver, BC to Toronto, ON) and jobs. So, I wanted to get back into posting here with a simple article on two types of design patterns for Python.
I’m also going to trying out hosting my code snippets in GitHub so they are easier to read, rather than struggling with formatting code blocks that inevitably get caught up spacing issues due to screen widths.
Why Use A Pattern?
This might be a review for most people, but using a software design pattern (should) helps in solving complex problems. Generally, the problem you’re trying to solve has a known solution or suggested method of solving. Meaning, you can spend more time on implementing the solution rather than searching for one or even creating one yourself.
Additionally, these patterns should provide us with the ability to update our project if the requirements change without fundamentally altering the core structure of the code.
In essence, our code is open for extension, closed for modification.
The Strategy Pattern
The main goal of this pattern is to switch out the same method to fit different situations, due to the encapsulation of the different strategy objects. From the image below our
Strategy class has a
AlgorithmInterface() method which is implemented differently by the classes
ConcreteStrategyC. To put things in perspective, I’ll show how this could be applied to something I’m rather familiar with … RPG characters. In particular, wizards.
You can have different kinds of wizards that have special abilities according to their type, cast different spells, and use different weapons. However, all wizard — at least the wizards we’ll be making — have a few things common to them. These are casting spells —
use_magic() — and attacking —
While we can create subclasses of each different wizard type, we know the thing that will be different to each of them will how they implement the use of the
use_weapon() method. We don’t care how they implement them, just that they are implemented.
We’ll use the strategy pattern to swap in and out different abilities (strategies) for each subclass of
Wizard, instead focusing on each of the object’s implementation of the abilities.
To put it in terms of design principles, we’ll be “programming to an interface, not an implementation.” And, in doing do reduce the coupling between our objects.
Defining our Strategies
First, we need to define the abilities (strategies) of a Wizard. As mentioned above, they will use some sort of weapon along with a few various spells. These spells have been split into two different strategies, offensive and defensive, and will be used to show inheritance between strategies and perform type checking — as much as Python will provide — on each of the Wizard’s spells and weapons.
We’re also going to be using
abc.ABCMeta to program the interface for our spells and weapons. Without going too deep, on the specifics of this is beyond the scope of this article however a good refresher can be found here.
Because we’re going to worry out the implementation of each strategy later but want to ensure they are implemented we will
abc.abstractmethod decorator on our base interface. A class that has a metaclass derived from
ABCMeta cannot be instantiated unless all of its abstract methods and properties are overridden. Meaning when we’re creating our new spell and weapon strategies, they must override
Because of the above, we don’t need to
raise NotImplementedError for our abstract methods, because having a metaclass of
abc.ABCMeta will throw an error for us if this is not implemented correctly.
We’re also going to be creating a
NULL strategy for weapons and for spells, as we might have some wizards which do not use weapons or uses fewer spells than others. We don’t want to have an error thrown when the Wizard object is created without a spell or weapon assigned to it so we’re just using the same patterning but implementing a strategy where nothing occurs.
Just as a note if you instantiate the base strategy or derived strategies without implementing the abstract method, you’ll get this error:
TypeError: Can't instantiate abstract class <<ClassName>> with abstract methods <<method_name>>
From the gist below, our basic strategy
MagicStrategy() has a
use_magic() method and we’ll be using this to create our different spells. but if implemented from this strategy nothing will happen and we’ll get an error at run time.
We have created three interfaces from the basic strategy
DefensiveMagicStrategy() that will allow us to further customize how our wizards behave.
NullMagicStrategy() allows us to give a wizard no spells, without an error being thrown. While the other two, will allow us to split our wizard’s abilities (strategies) by further defining them.
IceOffensiveMagicStrategy() will implement
HealDefensiveMagicStrategy() will implement
To create a further implementation of a strategy you simply have to make sure the new interface inherits from a previous interface, and implement the abstract methods.
Making Our Wizards
Or basic wizard has one attribute for a weapon and three attributes for spells. But what about wizards that are more specialized? Say, a wizard who uses fire spells or one that is devoted to healing?
Because we have our strategies sorted out before our wizards are created, we can easily create a base wizard class, define what types of spells or weapons we want them to use, and swap in different strategies as we see fit, rather than rewriting large chunks of code.
Alternatively we could only use a base
Wizard and simply swap in strategies, however I wanted to show how these strategies could be used along with inheritance.
The strength of the strategy pattern should become apparent here, as when we start swapping in different spell or weapon patterns, each strategy has a common interface to be called. This allows any instantiated wizard object to use
self.weapon_slot.use_spell_slot_2() and return the result of the strategy implemented there.
When a base wizard object is initialized, we can assign one weapon strategy and three spell strategies. Our base Wizard can use any type of weapon they want and any type of spell they want, but has no custom abilities.
FireWizard, they have one slot for any offensive spell type they want when instantiated, but as assigned by default a sword, two fire offensive magic spells, and a class specific method
Alternatively, our HealingWizard has no free spell attributes, is assigned a staff as a weapon, two healing magic spells, a
null spell in their third slot, and a class method
cast_mass_healing() which is specific to them.
Assigning Strategies to Wizards
Now that we have defined our strategies and the wizards to implement them, let’s test out our code. We’re going to create different strategy objects, and assign them to a
FireWizard and a
Notice how we can quickly instantiate two different
FireWizard objects, that have different strategies for their
self._spell_slot_3.use_magic(), yet both use the same method calls? That’s the speed and efficiency of the strategy pattern at work.
# Instantiate strategies
foms = FireOffensiveMagicStrategy()
mmoms = MMOffensiveMagicStrategy()
hdms = HealDefensiveMagicStrategy()
staff_ws = StaffWeaponStrategy()
sword_ws = SwordWeaponStrategy()
null_magic = NullMagicStrategy()
null_weapon = NullWeaponStrategy()# Create wizards
fw = FireWizard(mmoms)
fw2 = FireWizard(foms) # Assigning an additional fire spell
hw = HealingWizard()# Test spells
hw.cast_mass_healing()>> Casting MAGIC MISSILE spell
>> Casting FIRE spell
>> No spell assigned
>> Casting MASS HEAL
We can still assign the wrong spell to our wizards despite Python3’s limited type checking to assign our wizards different types of spell strategies.
What we would expect to happen if we try to assign an additional weapon to one of the spells slots is an error will be thrown, or if we try to assign a defensive spell to an offensive spell attribute. This is not the case because of the limited type checking in Python. That being said, you can implement some strategies to protect against this. However, those are beyond the scope of this article.
# Create a fire wizard with healing spellfw2 = FireWizard(hdms)
fw2._spell_slot_3.use_magic()>> Casting HEAL spell# Assigning hdms to the fire wizard was not caught during runtime
Just as a quick a side note, we can quickly create additional wizard interface (
SummonerWizard) on the fly by dynamically creating classes using the built-in
type method. This is essentially a dynamic form of defining a class, and more on that here. It’s not necessary by any case, but allows us to see a different way in creating and instantiating classes.
print('Casting SUMMON spell')soms = SummonOffensiveMagicStrategy()SummonerWizard = type(
'SummonerWizard', # Name of new Wizard
(Wizard, ), # Inherit interface
'cast_final_summon': # New method
lambda x: print('Casting FINAL SUMMON')
)# Create SummonerWizard instance and assign strategiessw = SummonerWizard(null_weapon, soms, soms, soms)
sw.use_spell_slot_1()>> No weapon assigned
>> Casting FINAL SUMMON
>> Casting SUMMON spell
I hope that was fairly straightforward, and you now know how to use this design pattern in a manner that is a bit more accessible than just looking at the class diagram.
Now for something a little different, as we’re moving away from tabletop gaming and into the kitchen. We’ll be using this setting, and the food prepared in it, to explain the command pattern.
This pattern is great to encapsulate method invocations and to decouple the invoker of a method from the receiver of the invocation. But what does that mean, exactly?
There is also a subtle difference between the terms invoke and execute use in the rest of the article, and this is the use of dependencies. In the case where execute is called, this implies your method will get executed, however, if a method is invoked, will call an entire chain of methods including the dependencies. This difference will be important for understanding how the command pattern works.
As well, while not strictly pythonic we’re going to be using a form of dependency injection (DI) when creating our classes. There is a good article here which goes into more of the details of this pattern.
# Dependency injection e.x. 1
def __init__(self, bar: Bar):
self._bar = bar # Class 'Bar' is injected# Dependency injection e.x. 2class Person:def __init__(self, profession: Profession):
self._profession = profession # Class 'Profession' is injected
You can see how both our
Person classes both require a separate class (
Profession) before they can be instantiated. Meaning they are dependent on these objects.
This is important, because creating objects directly within a class creates inflexible code which is hard to refactor. Using DI, the class becomes more reusable when it interacts with other classes, and makes things easier to test.
Now, back to the command pattern….
Let’s break down the various parts of this pattern before we get into how we’re going to implement it:
Commanddeclares an interface for
Receiverto for the specific method;
Commandinterface, implementing the
execute(). As well, the
Receiveris injected into the construction of this class;
ConcreteCommandobject and sets its
Receiver, and will use the invoker to
Invokerwhich will invoke each
Commandto carry out the request(s).
First we need to create a
Command which is an interface for executing a method. In this case, the method is
execute(). The same way we declared a spell and weapon interface above with
abc.ABCMeta, we’ll do it again here for this command.
Then we create a
ConcreteCommand which extends the
Command interface, implements
execute(), and a
Receiver is injected into the construction of the object.
None of this makes any sense without context. So let’s take a look at how we could make some lunch. Who does not like lunch?
In this case the class
Receiver) will hold the
make_sandwich() but it will not directly call this method. We want to call
execute() directly through an
Invoker which will then call
# Call chain from the InvokerInvoker.invoke() --> Command.execute() --> specific_command() --<<Something happens>>
Next, our class
SandwichCommand — which implements
Command — will inject
Sandwich as a parameter during instantiation.
# Create a Receiver, and a ConcreteCommand with Receiver as parametersandwich = Sandwich() # Receiver
command_sandwich = SandwichCommand(sandwich) # Injection
Before we get into the
Invoker object, let’s take a look at the whole body of our command pattern to set ourselves straight about the code mentioned above.
Invoker — without too much ceremony we’ll call ours
MealInvoker — will
execute() method indirectly through a
Command, and therefore make our sandwich. And the key point here is the
Invoker does not care what type of command it is being executed.
Additionally, with our
MealInvoker we can ask it to invoke a list of commands by adding them to a list to be called sequentially later.
Command Pattern So Far
Here are the steps with our command pattern:
- Create a
Commandclass interface with an
- Create a
Receiverclass which executes a specific method. In our case it is
- Implement the
Commandinterface and perform dependency injection with a specific
Receiveron the new
- Create an
Invokerclass which takes in different kinds of commands, and invokes
execute()to perform the specific (concrete) command on each
Remember how at the beginning of this section I mentioned this pattern “decouple(s) the invoker of a method from the receiver of the invocation”, now you should be able to see clearly how our
invoke() which in turn calls
execute() on a
ConcreteCommand, and finally the
Receiver calls its specific method —
make_sandwich() — completing the operations.
Let’s see how our command class works in practice
# Command pattern in actionsandwich = Sandwich() # receiver
command_sandwich = SandwichCommand(sandwich) # concrete commandsalad = Salad() # receiver
command_salad = SaladCommand(salad) # concrete commandmeal_invoker = MealInvoker(command_sandwich) # invoker
meal_invoker.invoke() # Starting the method callsmeal_invoker.add_command_to_list(command_salad)
meal_invoker.execute_commands()>> A sandwich is being made
>> A salad is being made
>> A sandwich is being made
>> A salad is being made
As I said earlier, it has been a while since I posted about anything. The move was a lot more than I expected … especially moving across the country. But I’m all settled in and I hope to be posting more and more over the coming months.
Thanks for reading, and I hope you learned something new.