If you are new to design patterns, the Strategy pattern is a great one to start with. You may have already used it and just didn’t know it. It is part of the Behavioral category of design patterns described from the Gang of Four (for more info, see my first article in this series).
Intent & Structure
The Strategy pattern gives you the ability to choose a “strategy” at runtime based on user input.
The Structure of the Strategy Pattern is comprised of:
- The Context
- The object invoking the strategy
- The IStrategy
- The interface defining the strategy
- The Strategy
- The concrete implementation of the strategy
When to Use the Strategy Pattern?
A good indicator might be when you have a method with multiple “if” statements. You are trying to handle different variations of behavior for a method. There’s also a good chance you may need to add more “if” statements in the future.
Example – Share a Podcast Episode
Let’s get into a specific example to help illustrate. In a podcast app, there is typically a “share” button to share a podcast episode. When you click on the share button, you are then presented with different options – share via SMS, Email, Facebook, Instagram, etc.
What if we had to write the Share() method for this screen shot (shout out Max Lugavere on the Genius Life!)? Would we have a bunch of “if” statements in our method to handle each type of sharing? If so, you code might look something like this below….
Sample Code:
What’s the problem with this?
Hopefully with all the scrolling you just had to do, you can see the issue of long-term maintainability. Readability. Lots of scrolling just to read through the method. Note that my example only has 4 types of sharing – my screenshot actually has 8+ ways of sharing. This has the potential to get extremely long. It’s easier to look at code with shorter methods rather than a long monstrous method. A noob might think it’s better having it all in one place. However, with all the if statements, it can make the code more error-prone when you need to make an update.
What if you found you needed to update the code for sharing via Instagram because their API changed or add new ways of sharing (which is not uncommon)? The “if” list will just keep growing and the method will continue to get longer. Additionally, every time you make an update to that method, you risk introducing a new bug and could potentially break the whole method so none of the sharing works.
How to Refactor with the Strategy Pattern?
Ok – so how can the Strategy Pattern help us? You could say each type of share should have its own “strategy” on how it will share a podcast. Based on the user’s input, the selected share type, at runtime, we can execute the right strategy.
Remembering the structure of the Strategy pattern – we can create a class diagram.
In this example, we have
- Context = ContextClass – a class needing to share user content
- IStrategy = IShareService interface
- Strategies – Concrete implementations of our interface
- InstagramShareService
- FacebookShareService
- EmailShareService
- TextShareService
Here is what the refactored code could look like:
The Context
This is the example class that is invoking the appropriate strategy at runtime. In our “OnShareTypeSelected()” method, we are now getting the right service from a factory (based on the factory pattern, which I will write about next). Don’t worry too much about how that works for now – it is just getting the right ShareService for us. If you want a sneak peek though, you can see a code snippet here of how my example factory looks.
The Strategy Interface
The Strategy Implementations
The Benefits
Yes – you do need to create more classes, but with the code broken up into smaller pieces, it is is much easier to read which increases its maintainability. If you did need to update one of the ways to share, such as Instagram, you would only need to update the InstagramShareService class and not affect any other code. If you needed to add more ways to share, you would just create a new class that implements the IShareService interface and make sure included in your ShareServiceFactory class. You wouldn’t even need to touch the ShareViewModel.cs class. You would also have an easier time unit testing each ShareService class individually.
Final Words
While you don’t need to use the strategy pattern for every time you encounter a few “if” statements, when you start to see the potential of a monster method and the “if” statement list is likely to grow – the strategy pattern may be a great design approach.