Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / DevOps / Agile

Create reusable component of partially common operation (method) using Strategy Design Pattern - Keep It Simple Series

4.85/5 (4 votes)
10 Jun 2019MIT6 min read 14.6K  
This article is a first article of a series called Keep It Simple. This article is about how we can create reusable component of partially common operation (method) using Strategy Design Pattern. We will see the need of Strategy Design Pattern.

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 - SwanAlbatross 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.

Bird Engine v1.0.0.0

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.

Bird Engine v1.0.0.1

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.

Image 3

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!

License

This article, along with any associated source code and files, is licensed under The MIT License