This chapter covers the Strategy pattern. It is also known as the Policy pattern.
GoF Definition
Define a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
Concept
A client can select an algorithm from a set of algorithms dynamically at runtime. This pattern also provides a simple way to use the selected algorithm.
You know that an object can have states and behaviors. And some of these behaviors may vary among the objects of a class. This pattern focuses on the changing behaviors that can be associated with an object at a specific time.
In our example, you see a Vehicle class. You can create a vehicle object using this class. Once a Vehicle object is created, you can add and set behaviors to this object. Inside the client code, you can replace the current behavior with a new behavior too. Most interestingly, you see that since the behaviors can be changed, the vehicle class is not defining the behavior; it is simply delegating the task to an object referenced by a vehicle. The overall implementation can make the concept clearer to you.
Real-World Example
In a soccer match, if Team A is leading 1–0 over Team B toward the end of the game, instead of attacking, Team A becomes defensive to maintain the lead. At the same time, Team B goes for an all-out attack to score the equalizer.
Computer-World Example
Suppose that you have a backup memory slot. If your primary memory is full, but you need to store more data, you can use a backup memory slot. If you do not have this backup memory slot and you try to store the additional data into your primary memory, the data is discarded (when the primary memory is full). In these cases, you may get exceptions, or you may encounter some peculiar behavior (based on the architecture of the program). So, a runtime check is necessary before you store the data. Then you can proceed further.
Implementation
You can see, inside the constructor, I set the initial behavior, which can be altered later using the SetVehicleBehavior(...) method . DisplayAboutMe() delegates the task to a particular object.
Class Diagram
Solution Explorer View
Demonstration
Output
Q&A Session
15.1 It appears to me that you are complicating everything by focusing on changing behaviors. Also, I do not understand why I need the Context class at all. You could simply use the inheritance mechanism and proceed. Can you please address these concerns?
If a behavior is common for all subtypes, it’s okay to use inheritance, for example, you can make an abstract class and put the common behavior into it so that all child classes get the common behavior. But the real power of strategy comes into picture when the behaviors can vary across the objects, and maintaining them using inheritance is difficult.
Now consider when you have lots of changing behaviors across objects like these. This kind of maintenance can be overhead.
Apart from this, let’s consider a special vehicle that has specialized features. If you simply put those special features in the abstract class, all other vehicle object inherits those and need to implement those. But it is not over yet. Further, assume that there is a constraint on the Boat class, which simply says that it cannot have any such special behavior. Now you encounter a deadlock situation. If you implement this special method, you are violating the constraint. If you do not implement it, the system architecture breaks because the language construct requires you to implement the behavior. (Or, you need to mark the class with the abstract keyword, but at the same time, remember that you cannot create an instance from an abstract class.)
To overcome this, I can create a separate inheritance hierarchy with an interface to hold all the specialized features, and my classes can implement the interface if needed. But again, it may solve the problem partially because the interface may contain multiple methods, and your class may need to implement only one of them. In the end, in any of these cases, the overall maintenance becomes tough. Apart from this, the special behaviors may change, and in that case, you need to track down all the classes that implement these behaviors.
In a situation like this, the context class acts as a savior. For example, for the Boat class object, the client does not set the fly behavior, or for Aeroplane class objects, the client does not set the float behavior; he simply knows which behavior is expected from the particular vehicle. So, if you want, you can guard against a situation in which a client mistakenly sets an incorrect behavior to a vehicle.
A “has-a” relationship fits better than an “is-a” relationship for this example, and it is one of the primary reasons that most of the design patterns encourage composition over inheritance.
15.2 What are the key advantages of using a Strategy design pattern?
This design pattern makes your classes independent from algorithms. Here a class delegates the algorithms to the strategy object (that encapsulates the algorithm) dynamically at runtime. So, the choice of algorithms is not bound at compile time.
It’s easier to maintain your codebase.
It’s easily extendable.
You can refer to the answer in Q&A 15.1 in this context.
15.3 What are the key challenges associated with a Strategy design pattern?
The addition of context classes causes more objects to exits in your application.
Users of the application must be aware of different strategies; otherwise, the output may surprise them.