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.
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.
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 -
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
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.
Change is the only constant in the software industry. Now in bird engine, we need to add one another
bird class called -
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 -
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?
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
Then we define two concrete subclasses -
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.
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
CannotFly concrete types. It can be set by
setFlyBehaviour(): It sets a concrete subclass type of
FlyBehaviour to the
flyBehaviour attribute. If concrete
Bird type is
flyBehaviour should be set to
CannotFly type. For other concrete
flyBehaviour should be set to
fly(): Now, this operation just calls the
fly() operation on
flyBehaviour attribute. Underlying concrete type (
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:
A picture is worth a thousand words.
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.
- Head First Design Patterns by O'Reilly
Thank you for your time. If you like this article, please rate it, share it and comment on it.