The Strategy Pattern
Strategy — defines a family of algorithms, encapsulate each one, and makes them interchangeable. Strategy lets the algorithm vary independently from the clients that use it.
Let’s take one of the most popular example of Duck simulation game to understand the Strategy design pattern. The game displays a large variety of duck species swimming and making quacking sounds.
The designer used standard OO techniques and created the following solution: Duck superclass from which all other duck types inherit
But now we also need ducks to fly. The designer went ahead confidently and came up with the following solution: A new method named fly was introduced in the superclass Duck and it was expected that all duck types will inherit the method that will enable each duck to fly. Very simple!
What’s the catch?
The catch here is that game also has the rubber duck which does not fly and the decoy duck which neither fly nor quack.
What designer thought was a great use of inheritance for the purpose of reuse hasn’t turned out so well when it comes to maintenance.
The designer experienced an epiphany and came up with an idea of interface and created two interfaces named Flyable and Quackable which can be implemented by any duck if it posses the fly or quack behaviour.
The solution looks good at first but it has a flaw. Can you think of that flaw in this design?
The flaw is that ducks can have multiple flying and quacking behaviours. So, if there is a need to change the flying behaviour of a group of ducks, we need to track down all the impacted ducks and update their behaviour individually. This would break the pattern of reusability. Can you think of a solution for this problem?
Fortunately, there is a design principle just for this situation which states that
Design Principle
Identify the aspects of your application and separate them from what stays the same.
How do we separate the varying part of our problem?
We know that fly() and quack() are part of the Duck class. To separate these behaviours, we will take these methods out of Duck class and create a new set of classes to represent each behaviour.
So how are we going to design the set of classes that implement fly and quack behaviour?
No worries! We have our second design principle:
Design Principle
Program to an interface, not an implementation
Here is the example of how we can implement the fly behaviour
In a similar way, we can implement the set of quack behaviours.
The next question should be how should we integrate the duck behaviour?
Here’s how
- We will add two instance variables to the Duck class named flyBehaviour and quackBehaviour.
- We will replace the methods fly() and quack() with new methods performFly() and performQuack()
Looks great! Now we can set duck behaviours while initialising each duck. And we just programmed to an interface and separated what varies that brought code reusability back to our application.
What if we want ducks to change their behaviours dynamically?
It’s no brainer, we can add two methods called setFlyBehaviour() and setQuackBehaviour() to the Duck class using which a programmer can update the behaviour of each duck dynamically.
Now, we are all set. We should celebrate this win!
Before going for the celebration, have you noticed the Has-A relationship: each duck has FlyBehaviour and QuackBehaviour to which it delegates flying and quacking.
When you put two classes together like this, you are using composition. This is an important technique which is our third design principle.
Design Principle
Favour composition over inheritance
Congratulations on your first pattern!
You just applied your first design pattern — the Strategy pattern.
Strategy — defines a family of algorithms, encapsulate each one, and makes them interchangeable. Strategy lets the algorithm vary independently from the clients that use it.