The strategy behavioral pattern allows you to define multiple ways to perform some action and then select the best approach.
This pattern has some similarities to the previous state design pattern. Both patterns describe how to change behavior. But the state design pattern is more focused on changing the behavior when some internal state changes causing the program to go into different modes. While the strategy pattern is more about exposing choices that can be selected based on what’s best.
A good example of this pattern is when an adventure game allows users to select their desired difficulty level before starting a new game. Each difficulty level performs the same basic task of determining how many and how powerful the various opponents will be. The rest of the game doesn’t need to worry about which difficulty strategy was selected. And each strategy is interchangeable because the user can select which one to use. Some games even let the user change the difficulty level after the game has started.
You could think of something like this as a state, especially for these user controlled strategies, but the overall idea is different enough to warrant its own design pattern. You probably wouldn’t think of changing the difficulty level as if it was a mode that can be changed like a drawing program would change the current tool in use or like how the adventure hero gets in and out of a boat. A difficulty level is more pervasive and controls how things are done throughout the game. It forms a broader strategy.
Listen to the full episode or you can also read the full transcript below.
The basic description says that this pattern defines a family of algorithms that are all encapsulated and interchangeable. It then lets the algorithm change independently of the client that uses it.
This pattern has some similarities to the previous pattern, state. But there are differences. Both patterns describe how to change behavior. But the state design pattern is more focused on changing the behavior when some internal state changes causing the program to go into different modes. The strategy pattern is more about exposing choices that can be selected based on what’s best. These choices are not usually something that’s available directly to the end user but they could be.
A good example of this pattern that’s usually made available to the user is when a game allows users to select their desired difficulty level before starting a new game. Each difficulty level performs the same basic task of determining how many and how powerful the various opponents will be.
This is what the description means when it says the strategies are encapsulated. The rest of the game doesn’t need to worry about which difficulty strategy was selected. And each one is interchangeable because the user can select which one to use. Some games even let the user change the difficulty level after the game has started.
You could think of something like this as a state especially for these user controlled strategies but the overall idea is different enough to warrant its own design pattern.
To me this pattern really shines when it’s made available for the developers to use. What do I mean by this?
Well, let’s examine various monsters or creatures in an adventure game. A small rat can move around and attack the hero. So can a troll. Sure, they’re going to have different health levels and inflict different amounts of damage but they’re also going to behave differently.
Maybe the rat prefers to run away and will fight only when surprised or cornered. And maybe a troll lays claim to a certain piece of land and will aggressively defend it.
As a developer, you’ll have a much easier time creating new monsters and adapting them to the game when you can choose what behavior a particular creature will have. This gives you the ability to create a giant rat creature with enhanced abilities above a normal rat and that also behaves just like a troll.
In other words, the behavior can be chosen separately from the object that exhibits that behavior.
You might also wonder what other uses this pattern can help with. Let’s take a different example of a user interface dialog box. This has controls such as text fields. Now let’s say one of the text fields should be used for entering numbers only. You need something to validate the user input and reject any entry that’s not a number. And another text field is used for a name so it has no such restriction. Instead of creating a NumericField control that derives from a TextField control and implements the extra validation, you could instead make the validation an optional strategy. The default strategy just accepts anything typed. But the developer of this dialog could configure the numeric control with a strategy that knows how to accept only numbers.
With a design such as this and with controls that each accept strategies, you can then apply the numeric strategy to other controls that also need it.
I’ll explain more about how to implement the strategy pattern right after this message from our sponsor.
( Message from Sponsor )
To implement the strategy design pattern, you start out by defining a base class or interface for all the different strategies. At least all the strategies that need to be swappable with each other. You might have completely different strategies for other purposes. You don’t need to make all strategies in your entire application have a common interface. Only those strategies that need to work together need to have a common interface with each other. The two example I gave earlier about the creature behavior and the dialog control validation are two separate strategies that don’t need to have the same interface. Different creature behaviors should have the same interface and different control validators should have their own interface.
Once you have a common strategy interface, you create concrete classes that implement the interface for different related strategies.
Then you have a class called a context class which contains a reference to a particular strategy. How it gets this reference is up to you. It could be specified in the constructor or through some other method. The context class may have a default strategy it can use if no other strategy is provided. Or maybe the context class requires a strategy and can’t even be constructed without one.
And the strategy might be changeable or it might be fixed. However you define the context class, this is the class that the other code uses. For the dialog controls example, the context class would probably be some base class that all controls derive from. This would set them all up to use the strategy pattern for validation.
Whenever the strategy methods need to be executed, you have a couple more options to choose from. How will the strategy obtain the information it needs to do its work?
Your first option is to have the context class pass the necessary information to the strategy methods. This reduces coupling between the context and the strategies since the context can treat each strategy the same and the strategies have no awareness of the context. Each strategy method is given everything it could possibly need.
The problem with this is that some strategies are likely to be more complicated than others. So you have to define the strategy methods with this in mind and pass everything that even the most complicated strategy could ever need. Simple strategies though won’t need all this and you end up doing a lot of work preparing to call strategy methods when the strategy may not even look at it.
A second option fixes this problem by passing the context itself to the strategy methods. This lets each strategy call back into the context to get whatever it needs. There’s no more wasted preparation since each strategy would only ask for additional information if it actually needs it.
This approach also has the downside that the strategies now need to know about the context and how to call methods in the context to get their required information. The strategies become more coupled to the context class and you’ll have a hard time trying to use a strategy someplace else with a different context.
If all this talk about contexts seems familiar, it’s probably because strategies are good candidates to use the flyweight design pattern. You can listen to a description of flyweights in episode 69.
Remember that software engineering involves tradeoffs just like any branch of engineering. If you want that bracket holding a shelf to be stronger, then it’ll likely weigh more too and need a stronger anchor into the wall. Or if you want to make a bracket stronger and lighter at the same time, then you may need to use a different material which could cost more. Everything is a tradeoff including your software decisions.