Strategy pattern
Definition
When using inheritance, it is a common practice to create methods that you expect subclasses to override, but there is a better way to customize class behavior. Instead of expecting subclasses to override your method, allow your constructor to accept these custom behaviors via parameters, then use these parameters to customize how your class behaves. In other words, create intentional
hooks
where you expect users would need to customize instance behaviors.
Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
The intent of the Strategy Pattern, suggests that this pattern is applicable when you have multiple algorithms and you want to treat them as independent objects that can be interchanged dynamically at runtime to achieve high cohesion and loose coupling in your application.
- Cohesion: https://en.wikipedia.org/wiki/Cohesion_%28computer_science%29
- Coupling: https://en.wikipedia.org/wiki/Coupling_%28computer_programming%29
The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from the clients that use it.
Introduction
In enterprise applications, you will often have objects that use multiple algorithms to implement some business requirements. A common example is a number sorting class that supports multiple sorting algorithms, such as bubble sort, merge sort, and quick sort. Similarly, a file compression class can support different compression algorithm, such as ZIP, GZIP, LZ4, or even a custom compression algorithm. Another example can be a data encryption class that encrypts data using different encryption algorithms, such as AES, TripleDES, and Blowfish. Typically, programmers tend to bundle all the algorithm logic in the host class, resulting in a monolithic class with multiple switch case or conditional statements. The following example shows the structure of one such class that supports multiple algorithms to encrypt data.
The above Encryptor class has conditional statements for different encryption algorithms. At runtime, the code loops through the statements to perform encryption based on the client specified algorithm. The result is a tightly coupled and rigid software that is difficult-to-change. You can imagine the consequences if you try to implement a new encryption algorithm, say TripleDES or a custom encryption algorithm. You’d have to open and modify the Encryptor class. Also, if an existing algorithm needs to be changed, the Encryptor class will again require modification. As you can see, our Encryptor class is a clear violation of the Open Closed principle – one of the SOLID design principles. As per the principle, new functionality should be added by writing new code, rather than modifying existing code. The violation occurred because we did not follow the fundamental tenet of Object-Oriented (OO) programming practice that states “encapsulate what varies”.
Such pitfalls in enterprise applications result in rippling effects across the application making the application fragile, and you can avoid them by using the Strategy pattern. Using the Strategy pattern, we define a set of related algorithm and encapsulate them in classes separated from the host class (Encryptor). Clients can choose the algorithm to use at run time. By doing so, we can easily add a new algorithm or remove an existing one without modifying the other existing algorithms or the host class. Each of the algorithm classes adhere to the Single Responsibility principle, another SOLID principle as they will only be concerned with encrypting data with a specific algorithm, which is currently lacking in our Encryptor class. In addition, with smaller algorithm classes, unit testing becomes easier to focus on testing one particular situation.
Participants
Using our example of data encryption, we will first implement the interface that will be used by all of the different encryption algorithm-specific classes. Let’s name the interface EncryptionStrategy and name the algorithm specific classes AesEncryptionStrategy and BlowfishEncryptionStrategy. Ultimately, these are our strategies.
We will next refactor the Encryptor class to remove all conditional statements and delegate any encryption request to an algorithm-specific class that the client specifies.
We can now summarize the participants of the strategy pattern as:
- Strategy (EncryptionStrategy): Is an interface common to all supported algorithm-specific classes.
- ConcreteStrategy (AesEncryptionStrategy and BlowfishEncryptionStrategy): Implements the algorithm using the Strategy interface.
- Context (Encryptor): Provides the interface to client for encrypting data. The Context maintains a reference to a Strategy object and is instantiated and initialized by clients with a ConcreteStrategy object.
Rewriting the example using the pattern:
In the EncryptionStrategy interface we wrote above, we declared a single encryptData() method that both the AesEncryptionStrategy and BlowfishEncryptionStrategy implements. In the AesEncryptionStrategy class, we used the AES encryption algorithm to symmetrically encrypt the string passed to encryptData(). We performed the encryption using the cryptography classes of the javax.crypto package. After encryption, we printed out the encrypted string. We performed the same function in the BlowfishEncryptionStrategy class, but this time using the Blowfish encryption algorithm.
Summary
To many, the Strategy and State patterns appear similar. It’s true that the structure of both the patterns are similar. It’s the intent that differs – that is, they solve different problems. The State pattern aims to facilitate state transition while the aim of the Strategy pattern is to change the behavior of a class by changing internal algorithm at runtime without modifying the class itself.
There is a lot of debate around the use of the Strategy Pattern with Spring. Often you’ll see the Strategy Pattern used in conjunction with Dependency Injection, where Springs IoC container is making the choice of which strategy to use. Different data sources as a great example. Using a H2 data source for local development is one strategy. Using MySQL for production is another strategy. Which one is to use at runtime is up to the Spring IoC container.
Spring 3, introduced a type conversion factory. In this, you provide a type converter, which implements the Converter interface. At run time, your code can ask the converter factory for the proper converter. This sure sounds like the Strategy Pattern, doesn’t it?
The Strategy Pattern is one of those GoF patterns you’ll often encounter without realizing it’s a classic GoF pattern. Its a pattern that will get used simply by practicing widely accepted OO development principles.
OO Basics used in this pattern
- Abstraction
- Encapsulation
- Polymorphism
- Inheritance
OO Principles used in this pattern
- Encapsulate what varies
- Depend upon abstractions - Program to an interface
- Favor composition over inheritance
Use case from Head First Design Patterns
Problem statement:
- Duck pond simulation game.
- The game can show a large variety of duck species swimming and making quacking sounds.
Initial design:
┌────────────────────────────────────────────────────────────┐
│Duck (superclass) │
│quack() - has implementation code │
│swim() - has implementation code │
│display() - abstract method because all ducks look different│
│// other duck-like methods │
└────────────────────────────────────────────────────────────┘
┌──────────────────────────────┐ ┌──────────────────────────┐ ┌───────────────────────────────────┐
│MallardDuck │ │RedheadDuck │ │and a lot of other type of ducks │
│display() { │ │display() { │ │that inherit from the duck class. │
│ //looks like a mallard duck │ │ //looks like a redhead │ │ │
│} │ │} │ └───────────────────────────────────┘
└──────────────────────────────┘ └──────────────────────────┘
- But now we need the ducks to fly().
- How to solve for it?
- We could put a fly() method in Duck superclass. But, not all ducks can fly. We cannot add behavior in the superclass that is not appropriate for some duck classes.
- We could ask the duck classes that don’t want the fly() behavior to override it and not do anything in that method.
- But what if we have some duck classes that don’t want the quack() or swim() behavior either?
Not all subclasses should have flying or quacking behavior. So, inheritance is not the right answer.
How about an interface? We can take the fly() out of the Duck superclass and make a Flyable interface with a fly() method in it. That way, only the ducks that are supposed to fly will implement the Flyable interface and have a fly() method.
That is not a good idea because of the following reasons.
- Interfaces don’t have implementation. So, no code reuse.
- What if we need to make a little change to the flying behavior? If there are many subclasses that are inheriting the Flyable interface, all those subclasses will have to be updated to take the new change into consideration. This approach completely destroys code reuse for those behaviors.
- Also, there can be more than one kind of flying behavior even for the ducks that do fly.
Design principle: Encapsulate what varies
Other than the problems with fly() and quack(), the Duck class is working fine. There are no other parts of it that appear to vary or change based on the type of duck subclasses.
Family or algorithms (or Set of behaviors):
We are going to create a set of classes for fly(). They will implement an interface called FlyBehavior. This set of classes will hold all the implementations of fly() behavior.
┌─────────────────────────┐
│ │
│ FlyBehavior (interface) │
│ fly() │
│ │
└─────────────────────────┘
┌───────────────────────────┐ ┌───────────────────┐
│ FlyWithWings (class) │ │ FlyNoWay (class) │
│ fly() { │ │ fly() { │
│ // one type of behavior │ │ // do not fly │
│ } │ │ } │
└───────────────────────────┘ └───────────────────┘
We are going to create a set of classes for quack(). They will implement an interface called QuackBehavior. One of these classes will implement “quacking” behavior, another will implement “squeaking” behavior and another will implement “silence” behavior.
For these set of classes, the entire reason for their existence is to represent a behavior (e.g. quacking, squeaking, silence, etc.)
This is different from the initial design where a behavior comes from a concrete implementation in the Duck superclass, or by providing a specialized implementation in each subclass.
With the new design, each duck subclass will use a behavior represented by an interface (FlyBehavior, QuackBehavior, etc.). The actual implementation of the behavior will not be locked into each or any of the duck subclasses.
The key is that a duck will now delegate its flying and quacking behavior, instead of using quacking and flying methods defined in Duck superclass (or subclass).
┌─────────────────────────────┐
│ Duck (superclass) │
│ │
│ FlyBehavior flyBehaviour │
│ QuackBehavior quackBehavior │
│ │
│ performFly() │
│ performQuack │
│ swim() │
│ display() │
│ // other duck-like methods │
└─────────────────────────────┘
How will the flyBehaviour and quackBehavior instance variables be set? Like this:
public class MallardDuck extends Duck {
public MallardDuck() {
quackBehavior = new Quack();
flyBehaviour = new FlyWithWings();
}
// and other methods that need to be implemented in MallardDuck
}
When a MallardDuck is instantiated, its constructor initializes the MallardDuck’s inherited quackBehavior instance variable to a new instance of type Quack and it’s inherited flyBehaviour instance variable to a new instance of type FlyWithWings.
But didn’t we say we should not program to an implementation? Isn’t that what we are doing in the constructor? We are making a new instance of a concrete Quack implementation class and a concrete FlyWithWings class.
Yes, for now. There are other patterns that can help fix it.
How can we set behavior dynamically?
By adding two new methods to the Duck class:
public void setFlyBehavior(FlyBehavior fb) {
flyBehaviour = fb;
}
public void setQuackBehavior(QuackBehavior qb) {
quackBehavior = qb;
}
In the duck subclasses,
public class ModelDuck extends Duck {
public ModelDuck() {
flyBehaviour = new FlyNoWay();
quackBehavior = new Quack();
}
// and other methods that need to be implemented in MallardDuck
}
Make a new FlyBehavior type
public class FlyRocketPowered implements FlyBehavior {
public void fly() {
System.out.println("I am flying with a rocket!")
}
}
To change a duck’s behavior at runtime, just call the duck’s setter method for that behavior. Make the ModelDuck rocket-enabled:
Duck model = new ModelDuck();
model.performFly();
model.setFlyBehavior(new FlyRocketPowered());
model.performFly();
Design principle: Depend upon abstractions - Program to an interface
Design principle: Favor composition over inheritance
-
HAS-A is better than IS-A
Each duck has a FlyBehavior and QuackBehavior to which it delegates flying and quacking.
When you put classes together like this, you are using
composition
. Instead of inheriting their behavior, the ducks get their behavior by being composed with the right behavior object.Creating systems using composition gives you a lot more flexibility. Not only does it let you encapsulate a family of algorithms into their own set of classes, it also lets you change behavior at runtime as long as the object you are composing with implements the correct behavior interface.
Composition is used in many design patterns and we will see a lot more about its advantages and disadvantages.
Design principle: Favor composition over inheritance