Today, I'm going to write about the strategy design pattern and give some examples on where it should be used and why it's useful. I'm really sleepy, only had 3 hours of sleep, so I hope I can do this quick and off the top of my head.
The Problem and Design Smell
Suppose we have the following class hierarchy:
We have an abstract Animal class, that has two virtual methods Eat() and MakeNoise(). Both household Cat and household Dog classes inherit from Animal, along with inheriting behavior animal behavior. Inheriting behavior typically leads to bad design and maintainability. Let's look at the code implementing the classes:
When we run these classes, we get the following output:
The problem ('design smell') associated with this approach is simply the way we are placing the implementation of the actions Eat() and MakeNoise() inside the concrete classes. This doesn't exactly lead to a maintainable design. For example, suppose we want to extend our animal kingdom by introducing an AllyCat class. An AllyCat might have different implementations of Eat() (eating out of the trash), but still have the same kind of MakeNoise() (moew) implementation. You might think, "well, we can just derive from the concrete class Cat, and override the Eat() method with our own AllyCat.Eat() implementation" and you're right, you can. However, extending behavior through hierarchal inheritance should be avoided. Not only that, designs like this can lead to duplicate code.
Instead, you should compose classes with isolated algorithm implementations. The goal is to separate varying-code from non-varying code. A quick way to identify non-varying code aspects of your classes is through "has-a" relationships. A Dog "has-a" distinct eating behavior, a Cat "has-a" distinct eating behavior, an AllyCat "has-a" distinct eating behavior, so on and so forth... From our classes and implementation, we have noise behaviors and eating behaviors.
The solution to basic problems is to implement a Strategy pattern. Consider the following architecture:
And taking a peek in some of the Cat, Ally Cat and Dog code:
All that we have done with this new architecture is delegate the "making noise" and "eating" to behavioral classes that specifically handle the task. We have delegated control to the behavioral classes. This is a much better extensible and coherent design and often leads itself to better to code reuse.
Whenever we create classes that are composed of behavioral actions, we call these compositions. Classes such as Dog, Cat, and AllyCat are compositions of behaviors and gain their behavior through composition, not inheritance. Inheritance (like the previous architecture) can lead to unintended consequences when fiddling with the superclass and is more prone to breaking existing code.
- Algorithm Families – A general case different algorithms need to run depending on a particular situation.
- Sorting - Used in sorting when different types of sort algorithms need to be applied to a collection. For example, List.SortBy(FirstNameStrategy)
- Behavioral – Used when many distinct "behaviors" are expected of derived classes.
Naming Keywords for the pattern
When naming your interfaces, here are some keywords to include in the naming of your strategy
General: Strategy, Algorithm
- Example: CaliforniaTaxStrategy
Behavioral: Behavior, Action, Style
Principals to take away from this strategy
Formal definition of the strategy pattern
The 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.
This blog post is a little dry because I'm really sleepy, I'll try updating it to make it a little more clear later when I have more time. But I hope some of you understand! Later I'll show you how to improve this architecture.