The Strategy design pattern is one of the most fundamental and commonly-used patterns in modern object-oriented design. Take some time to make sure you're proficient with it.
The Strategy design pattern is one of the most fundamental and commonly-used patterns in modern object-oriented design. Take some time to make sure you're proficient with it.
Thanks to DevIQ for sponsoring this episode! Check out their list of available courses and how-to videos.
I'm continuing a small series of tips on design patterns that I started with episode 17. I encourage you to listen to these tips in order, since in many cases they'll build on one another. This week I want to briefly describe the strategy pattern, and more importantly, why I think it's a pattern every object-oriented software developer should know.
The strategy design pattern is often applied as a refactoring technique to improve the design of some existing code. The original code likely has some tightly-coupled and/or complex logic in it that would be better off separated from the current implementation. I think one reason why I'm so fond of the strategy pattern is that it literally helps with every one of the SOLID principles of object-oriented design. In my SOLID course on Pluralsight, I also discuss the Don't Repeat Yourself, or DRY, principle, which strategy can help with as well. Let's look at how.
First, if you have a class that's doing too much (therefore breaking SRP - Single Responsibility Principle), common refactorings like extract method and move method can be used to pull logic out of one big method. However, if you then call this method from the big method, either statically or by directly instantiating a class to which you've moved the logic, you're not helping the coupling aspect of the problem. We'll get to that when we get to the 'D' in SOLID. Applying the strategy design pattern in this case is really just a slight twist on the usual extract and move method refactorings. You're still doing that, but you also typically create a new interface and pass in the interface to the original code. After moving the original implementation code to a new class that implements the new interface, you should have a new class that follows SRP and your original class should at least have fewer responsibilities.
Considering this refactoring I just described, it's easy to see how it can help with the Open/Closed principle, or OCP, too. Whereas the original code's complex logic would have needed modified and recompiled any time a change was requested, the new design can accommodate changes in the implementation of extracted method by writing new code that implements the same interface. Then, an instance of the new class that has this new implementation can be passed into the existing code without touching the existing code. I talked about how important this is with legacy code in episode 15.
Of course, if you do have multiple implementations of your abstract types, it's important that they all behave as advertised, otherwise you may encounter runtime exceptions. Ensuring that any implementation you write that inherits from another type, whether an interface or a class, means following the Liskov Substitution Principle, or LSP.
Following LSP is much easier when the base type's behavior is fairly small. Large interfaces require much more effort to fully implement that smaller ones. The Interface Segregation Principle, or ISP, suggests keeping interfaces small and cohesive, so that client code doesn't need to depend on behavior it doesn't use. Done properly, the interfaces you create while implementing the strategy design pattern should be tightly focused.
That brings us to the Dependency Inversion Principle, or DIP. This is really what the strategy pattern is all about. Whereas the initial code was tightly coupled to a specific implementation, the refactored version of the original method now depends on an abstraction. Instead of the original method deciding how to do the work, the code that calls the method makes that decision by deciding which implementation of the interface to provide. If you're familiar with dependency injection, then the strategy pattern should already be familiar to you. Make sure you're comfortable with pulling out dependencies when you discover them, though. The method extract and interface creation aspects of the strategy pattern aren't always emphasized when dependency injection is discussed.
We're out of time for this week but I'll mention that the strategy pattern also helps with the DRY principle by creating a single place for a particular implementation to live, as well as the Explicit Dependencies Principle, by ensuring classes request their dependencies rather than hiding them in their methods. You can learn more about these principles from the show notes at weeklydevtips.com/019.
Would your team or application benefit from an application assessment, highlighting potential problem areas and identifying a path toward better maintainability? Contact me at ardalis.com and let's see how I can help.