Introduction
Before we start, let me answer the below questions.
- What is the problem?
- Why we need Strategy Design Pattern in order to solve the problem?
Let me explain the problem by taking one example. There is one superclass – S. It has four subclasses – A, B, C, and D. There is one operation (you can say a method) O, which has two versions of implementation. One version of an operation O is common to subclasses – A and C. Another version of an operation O is common to subclasses – B and D. We cannot simply put this operation O in super class S as operation O has two versions of implementation and these two versions are not common to all the subclasses. Each version of an operation O is partially common.
One Solution
We can implement a specific needed version of an operation O to each subclass. But here we are repeating ourselves. A good design doesn’t have repetition. Repetition makes the system hard to maintain, hard to extend and vulnerable to bugs. So, this solution is not a perfect solution.
Much Cleaner Solution
Use Strategy Design Pattern!
Don’t worry if you still don’t get the problem. We will discuss this throughout the article.
Now let’s understand how our superhero Strategy Design Pattern will save our day.
Background
There is one simple Bird encyclopedia system. It simulates the bird's behaviour like flying, swimming, etc. Under the hood, there is one bird engine, which makes all these things possible. Let's discuss an initial design of the bird engine. There is one superclass called Bird
. It is an abstract
class and has all the common operation (method) implementation. There are three concrete classes of birds - Swan
, Albatross
and Gull
, which inherits the Bird
class. Let's see the diagram so we can understand things better. Please note one thing here, we never go to code level nitty-gritty details as it is a design article. Keep it simple.
As per the above diagram:
Bird
class has the following operations (methods):
fly()
: Implementation of flying logic swim()
: Implementation of swimming logic walk()
: Implementation of walking logic makeSomeNoise()
: It is an abstract (an operation without implementation, just a skeleton) operation. Every bird has a unique voice. So, every subclass has to provide its own implementation display()
: It is an abstract operation. Every bird has a unique appearance. So, every subclass has to provide its own implementation.
Swan
class has the following operations:
makeSomeNoise()
: Implementation of swan voice display()
: Implementation of swan appearance
and the same thing for Albatross
and Gull
classes.
All the common bird operations are defined in Bird
superclass so that we don't need to repeat ourselves in concrete bird classes. A good design always encourages reusability.
Problem Arises
Change is the only constant in the software industry. Now in bird engine, we need to add another bird
class called - Penguin
. Penguin
class has to inherit Bird
class as per our design. So, Penguin
class inherits all the operations of Bird class. But the problem is Bird
class has fly()
operation and our poor penguin cannot fly. Unless penguin is Skipper from Madagascar. What should we do now?
We can pull out fly()
operation from Bird
class and implement it to all concrete subclasses - Swan
, Albatross
and Gull
. But if we do that, then we compromise reusability and we are repeating ourselves in all the concrete subclasses including Penguin
class. As Penguin
class should implement fly()
operation with no fly implementation.
So, what should we do in order to solve the problem in a cleaner way?
Solution Sunshine
In order to solve this problem in a cleaner way, we pull out the implementation of fly()
operation from Bird
class. Then we create one FlyBehaviour
abstract class and define a fly()
abstract operation in it.
FlyBehaviour
abstract class has the following abstract operation:
fly()
: It is an abstract operation as we have two versions of fly()
operation.
Then we define two concrete subclasses - CanFly
and CannotFly
, which inherit FlyBehaviour
abstract class.
CanFly
class has the following operations:
fly()
: Implementation of a bird flying
CannotFly
class has the following operations:
fly()
: Do nothing as it is for those birds, who can't fly.
Let's see the updated diagram instead of making things more complex.
Here, FlyBehaviour
is an interface as it doesn't have an implementation.
Bird
abstract class structure updated only:
flyBehaviour
: It is an attribute of a type FlyBehaviour
. It can hold CanFly
or CannotFly
concrete types. It can be set by setFlyBehaviour()
operation. setFlyBehaviour()
: It sets a concrete subclass type of FlyBehaviour
to the flyBehaviour
attribute. If concrete Bird
type is Penguin
, then flyBehaviour
should be set to CannotFly
type. For other concrete Bird
types, flyBehaviour
should be set to CanFly
type. fly()
: Now, this operation just calls the fly()
operation on flyBehaviour
attribute. Underlying concrete type (CanFly
or CannotFly
) of FlyBehaviour
handles the operation behaviour.
This is called composition. Composition of Bird
type and FlyBheaviour
type. Identify the aspects of your application that vary and separate them from what stays the same. This way, we can create a resuable component of partially common operation.
Another Problem Arises
Now we need to add one more bird to our bird engine called - Frigatebird
. So, our Frigatebird
class has to inherit Bird
class as per design. So, Frigatebird
class inherits all the operations of Bird
class. But the problem is our poor Frigatebird
can't swim. What should we do now?
You better know what we should do now. Try to solve this problem by yourself. you can find its solution right below but don't do cheating.
Solution Sunshine Again
I am not going to repeat myself again. So I let the diagram speak as you know:
Quote:
A picture is worth a thousand words.
Conclusion
The design solution that we have used in order to create a reusable component of the partially common operation is called - Strategy Design Pattern. Now our bird engine design is so flexible, that we can add more concrete bird types without changing other classes. All we need to do is just add class, that's it. Every engine should be designed in such a way that it encourages reusability, extensibility and maintainability. At the end of the day, a good design will save you lots of effort and lots of money also. The goal of each and every design pattern is to create a reusable, extendable and maintainable system.
Now it is a time of official definition of Strategy Design Pattern: Defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
References
- Head First Design Patterns by O'Reilly
Last Note
Thank you for your time. If you like this article, please rate it, share it and comment on it.
Happy reading!