Design Patterns - Strategy Pattern

Design Patterns - Strategy Pattern

Strategy pattern is a sensible way to start if you're learning design patterns. It's actually probably the simplest pattern, Although it is easy, it is difficult for some developers to understand it, because it solves a complex problem in the software design, and people get confused whenever tutorials, books, and online articles try to explain the Strategy pattern because of the problem it solves, not the pattern itself.

In this article, we will try to isolate the Strategy pattern idea from the problem that this pattern supposes to solve with a simple example and implementation, then we will go through the problem and combine them both at the end.

"Also, you can view the code that I used in this article as an example. The link is at the end of the article."

Strategy Pattern

Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

The word algorithms here means "any implementation" It can be an algorithm, a call to an API, or any functionality.

Strategy pattern is known as Policy Pattern. We define multiple algorithms and let the client application pass the algorithm to be used as a parameter. One of the best examples of the strategy pattern is the Collections.sort() method that takes the Comparator parameter. Based on the different implementations of the Comparator interface, the Objects are getting sorted in different ways.

The strategy pattern is more about promoting the use of compositions rather than inheritance. So, when you think about how this pattern can help you, do not think about (how we can apply it in the payment module, or how to use it in the notifications module) it's not a structural pattern or creational pattern, it's a behavior pattern and more about understanding that inheritance is not intended for code reuse.

In other words, if you've got some aspect of your code that is changing, say with every new requirement, you know you've got a behavior that needs to be pulled out and separated from all the stuff that doesn't change.

Strategy Pattern Roles:

The roles of applying the strategy pattern are simple:

  1. You must have two or more different implementations of the same function/feature.
  2. You want to change the behavior of the function in the run-time and switch back and forth between them based on the client's decision.

Example

We are developing a system that can simulate the different types of entertainment that people can do during their workday. They can play with the PlayStation, play with Xbox, or go to the GYM. And there are two things people can do (play or watch others play).

No alt text provided for this image

In the above diagram, we created the entertainment concrete class as a parent class and the three child classes or subclasses (PlayStation, Xbox, and GYM) which inherit the superclass Entertainment class.


Entertainment.java        
No alt text provided for this image
PlayStation.java        
No alt text provided for this image
Xbox.java        
No alt text provided for this image
GYM.java        
No alt text provided for this image

In Our Main class, we will instantiate the PlayStation class, Xbox class, and GYM class, and call the play() method and watchOthersPlayMethod()

No alt text provided for this image
No alt text provided for this image

When we run the program we will see that we have the same implementation for our three classes.

Here we need to define more business logic about the system:

  1. Watching others play can be the same among all the subclasses because it requires no effort to watch and almost you will do the same behavior, just setting down and looking at other players. So, it can be inherited from the superclass "Entertainment".
  2. Playing PlayStation is like playing Xbox.
  3. Playing PlayStation and Xbox is not like playing at the GYM, so we have two playing implementations, one for playing PlayStation and Xbox and the other one for playing at the GYM.
  4. The business team can add some more entertainment staff like Chess or Backgammon which require more thinking skills so that the play() method implementation can be different from the two that we already have.

By keeping those in mind we know that the above design will not be going to work. What we need to do is to apply the design principle that says "Identify the aspects of your application that vary and separate them from what stays the same". And this is what we will do and this is what Strategy Pattern is all about.

First, we will take the parts that vary and encapsulate them, so that later we can alter or extend the parts that vary without affecting those that don't.

We know that play() method is the part of the Entertainment class that varies across the games. To have all the playing implementations we need to separate the play() method from the Entertainment class and create a new set of classes to represent each playing implementation.

We have two playing strategies:

  1. A playing strategy that requires "mind-to-hand" skills to play PlayStation and Xbox will call it (PlayHandStrategy).
  2. A playing strategy that requires body strength to play at GYM will call it (PlayPhysicalStrategy).

No alt text provided for this image
No alt text provided for this image
No alt text provided for this image
No alt text provided for this image

The Entertainment is still a superclass, but we are pulling out the play() implementations and putting them into other classes. By doing so we took out the parts that vary and encapsulate them.

Second, we need to connect the two strategies we made with the Entrainment class to allow the client to choose the playing strategy. This will lead us to the second design principle "Program to an interface, not an implementation" that way the entertainment subclasses won't need to know any of the implementation details for their own behaviors.

Note that, "Program to an interface" really means "Program to a supertype".

We will use Polymorphism to do this by using an interface to represent all the strategies "for instance PlayStrategy" and each playing strategy (PlayHandStrategy, PlayPhsicalStrategy, etc.) will implement the PlayStrategy interface.

No alt text provided for this image
No alt text provided for this image
No alt text provided for this image
No alt text provided for this image

Third, we will integrate the Entertainment class with the PlayStrategy interface. The key is that the Entertainment class will now delegate its playing implementation, instead of using the play() method defined in the Entertainment class (or subclass).

No alt text provided for this image
No alt text provided for this image

In the above diagram, we added an instance variable to the Entertainment class called playStrategy that declared as interface type (not concrete class implementation type). Each entertainment object will set this variable polymorphically to reference the specific strategy it would like at runtime (mind-to-hand skills or full body strength).

We also added one method called executePlay() in the Entertainment class to execute or call the play() method in the chosen strategy that the client selected at the runtime.

At the creation of the subclasses, we will define which strategy this class will use in the constructor.

No alt text provided for this image
No alt text provided for this image
No alt text provided for this image

In the Main class, we don't have to change a lot we just will call the executePlay() instead of the play() method that we removed from the Entertainment class.

No alt text provided for this image
No alt text provided for this image

Now our design is flexible and extendable, and if we have a new requirement let's say to implement a Chess game that requires more thinking skills, we can do that easily without affecting the overall design or affecting the other playing strategies. All we have to do is to create a new concrete class called Chess that extends the Entertainment class.

No alt text provided for this image

The Chess game doesn't require physical strength or mind-to-hand. So, to play Chess you have to be smart and have more thinking skills. This means that we can not use one of the strategies that we have and we need to create a new playing strategy that depends on thinking skills.

No alt text provided for this image

In the above diagram, we add a new concrete class called PlayMindStrategy that implements the PlayStrategy interface which has the required thinking skill to play. Now the client can play Chess by using the PlayMindStrategy.

No alt text provided for this image
No alt text provided for this image
No alt text provided for this image
No alt text provided for this image

By having a HAS-A relationship each entertainment has a playStrategy to which it delegates playing. When you put two classes together like this you're using composition. Instead of inheriting their implementation, the entertainment object gets its implementation by being composed of the right strategy object.

If we have another requirement to add Backgammon which requires also thinking skills all we have to do is to create another concrete class called Backgammon and extend the Entertainment class, and Backgammon can use the PlayMindStrategy implementation.

No alt text provided for this image
No alt text provided for this image
No alt text provided for this image

The HAS-A relationship is an interesting one. Actually, this is an important technique; in fact, we've been using the third design principle: (Favor composition over inheritance).

The problem that Strategy Pattern Solve

To understand the problem that the strategy pattern solves, we must identify why we pulled out the implementation of the play() method from the Entertainment class and separated the implementation into different concrete classes, instead of overriding the play() method in the GYM class and let the GYM class have its own implementation.

No alt text provided for this image

The above diagram is the base diagram that we introduced at the beginning of this article. And yes if we override the play() method in the GYM class, it will fix our problem, but it will fix our problem here and now in this situation only.

For example, if we changed the base implementation of the play() method in the Entrainment class to have a "mind-to-hand" implementation and overridden the play() method in the GYM class to have a "body strength" implementation, it will give us what we need here.

Entertainment.java        
No alt text provided for this image
GYM.java        
No alt text provided for this image

When we run the program it will work just fine as expected.

No alt text provided for this image

In the PlayStation, it used the "mind-to-hand" implementation that exists in the Entertainment superclass. The Xbox also used the "mind-to-hand" implementation that exists in the Entertainment superclass. In the GYM it overrides the play() method implementation that was inherited from the Entertainment superclass and it provided its own implementation, the "body strength".

Now we will add Chess to our program. So we will create a Chess class that extends the Entertainment class.

No alt text provided for this image

As we mentioned before the Chess game requires thinking skills so it will have its own implementation like the GYM.

Chess.java        
No alt text provided for this image

When we instantiate the Chess class in our Main class and run the program it also will work fine.

No alt text provided for this image
No alt text provided for this image

So far so good, then what is the problem here?!

The problem here is that we cannot add any class that uses "body strength" like Running or uses "thinking skills" like Backgammon. Because if we added those classes we will end up with code duplication and we cannot work around this by making them the base playing method because we already have a base that contains the "mind-to-hand" implementation.

No alt text provided for this image
No alt text provided for this image

As we see in the above code screenshot, the two classes will have the same implementation of the play() method. This is exactly what Strategy Pattern solves. Inheritance is not intended for code reuse. If you have a simple implementation inheritance can be good, but if your code keeps changing and you have to keep adding functionalities or features at some point it will not be good as it seems, it may become your nightmare. So, what you need is to identify the parts that vary in the program and separate them so you don't end up with an overlapped logic situation.


I want you to take a moment and think about the above problem and try to solve it on your own. If you find a fix or have another idea to solve it, please share it with me.


Conclusion

The Strategy pattern defines multiple implementations and lets the client application pass the implementation to be used as a parameter.

Strategy pattern is about promoting the use of compositions rather than inheritance. Creating systems using the Strategy pattern gives us a lot more flexibility. Not only does it let us encapsulate a family of algorithms into their own set of classes, but it also lets us change behavior at runtime as long as the object we're composing implements the correct behavior interface.

Sample

You can view the above example code on?GitHub.

要查看或添加评论,请登录

Shehab Salah的更多文章

  • How are Java Objects Stored in Memory? And What are the Stak and the Heap?

    How are Java Objects Stored in Memory? And What are the Stak and the Heap?

    To run an application in an optimal way, JVM divides memory into stack and heap memory. Whenever we declare new…

    1 条评论
  • Spring Data JPA - Deeper Look

    Spring Data JPA - Deeper Look

    After we discussed the difference between Hibernate and Spring Data JPA, let's take a deeper look at Spring Data JPA…

  • DAO Pattern

    DAO Pattern

    Code that depends on specific features of data resources ties together business logic with data access logic. This…

  • MVP for Android

    MVP for Android

    Why use architecture? There are plenty of articles and examples about the MVP architecture and there are a lot of…

  • Difference Between Hibernate and Spring Data JPA

    Difference Between Hibernate and Spring Data JPA

    In this quick article, I would like to describe what is the difference between Hibernate ORM framework and Spring Data…

    1 条评论

社区洞察

其他会员也浏览了