July 11 2024
Sponsored
• Transform your API development process with Postman Flows! Experience a new way to visually create, debug, and automate complex API workflows with ease. Dive into the future of API management and enhance your productivity here.
Many thanks to the sponsors who make it possible for this newsletter to be free for readers. Become a sponsor.
The Strategy pattern is a behavioral design pattern that defines a family of algorithms, encapsulates each one, and makes them interchangeable.
The Strategy pattern lets the algorithm vary independently from clients that use it. What this means?
Consider a scenario where you need to travel from one place to another and to calculate travel time. You have several options for transportation - car, bus, bike, and walking. Each mode of transportation is a different strategy for traveling.
If you have a monolithic design where the mode of travel is hard-coded or tightly coupled with the rest of the travel planning logic, any change in travel options or the addition of new modes becomes a cumbersome task.
The Strategy pattern elegantly solves these issues by encapsulating the travel modes into separate strategies.
This means you can easily add a new mode of transport, like a taxi or motorcycle option, without disturbing the rest of your code. Each travel mode, be it a car, bike, or bus, gets its own class with a common interface.
Let's see how it looks with a classic switch statement:
using System; public class TravelTimeCalculator{ public void CalculateTravelTime(string travelMode, double distance) { switch (travelMode) { case "Car": Console.WriteLine($"Travel time by Car: {distance / 60} hours."); break; case "Bus": Console.WriteLine($"Travel time by Bus: {distance / 40} hours."); break; case "Bike": Console.WriteLine($"Travel time by Bike: {distance / 15} hours."); break; case "Walking": Console.WriteLine($"Travel time by Walking: {distance / 5} hours."); break; default: throw new ArgumentException("Invalid travel mode"); } }} public class Client{ public static void Main(string[] args) { var calculator = new TravelTimeCalculator(); calculator.CalculateTravelTime("Car", 120); calculator.CalculateTravelTime("Bus", 120); calculator.CalculateTravelTime("Bike", 120); calculator.CalculateTravelTime("Walking", 120); }}
Imagine that this switch has another 5,6 conditions, it would be difficult to follow them all.
The Strategy pattern eliminates the need for conditionals by encapsulating each algorithm in its own class, all adhering to a common interface.
Rather than implementing all algorithm variations, the original object delegates the execution to one of these encapsulated classes.
Now, let's refactor the above code to use the Strategy pattern.
1. Define the Strategy Interface: Defines a common interface for all travel strategies.
public interface ITravelStrategy{ void CalculateTravelTime(double distance);}
2. Concrete Strategies:
Implement the travel time calculation for each travel mode (CarTravelStrategy, BusTravelStrategy, BikeTravelStrategy, WalkingTravelStrategy).
public class CarTravelStrategy : ITravelStrategy{ public void CalculateTravelTime(double distance) { Console.WriteLine($"Travel time by Car: {distance / 60} hours."); }} public class BusTravelStrategy : ITravelStrategy{ public void CalculateTravelTime(double distance) { Console.WriteLine($"Travel time by Bus: {distance / 40} hours."); }} public class BikeTravelStrategy : ITravelStrategy{ public void CalculateTravelTime(double distance) { Console.WriteLine($"Travel time by Bike: {distance / 15} hours."); }} public class WalkingTravelStrategy : ITravelStrategy{ public void CalculateTravelTime(double distance) { Console.WriteLine($"Travel time by Walking: {distance / 5} hours."); }}
3. Context Class:
Uses a strategy object to perform the travel time calculation, allowing the strategy to be set dynamically.
public class TravelContext{ private ITravelStrategy _travelStrategy; public void SetTravelStrategy(ITravelStrategy travelStrategy) { _travelStrategy = travelStrategy; } public void CalculateTravelTime(double distance) { _travelStrategy?.CalculateTravelTime(distance); }}
4. Context Class:
Uses a strategy object to perform the travel time calculation, allowing the strategy to be set dynamically.
public class Client{ public static void Main(string[] args) { var travelContext = new TravelContext(); // Travel by Car travelContext.SetTravelStrategy(new CarTravelStrategy()); travelContext.CalculateTravelTime(120); // Travel by Bus travelContext.SetTravelStrategy(new BusTravelStrategy()); travelContext.CalculateTravelTime(120); // Travel by Bike travelContext.SetTravelStrategy(new BikeTravelStrategy()); travelContext.CalculateTravelTime(120); // Travel by Walking travelContext.SetTravelStrategy(new WalkingTravelStrategy()); travelContext.CalculateTravelTime(120); }}
1. Eliminates Large Switch/Case Statements:
The complex conditional logic is replaced with a more flexible and maintainable design.
2. Encapsulation:
Each travel mode's logic is encapsulated in its own class
3. Extensibility:
New travel modes can be added without modifying existing code.
Let's demonstrate the extensibility of the Strategy pattern by adding a new travel mode without modifying the existing code.
Initially, we will have travel modes like Car, Bus, Bike, and Walking.
Then, we will add a new travel mode, Train, to show how it can be done without changing the existing code structure.
To add a new travel mode (Train), we simply create a new concrete strategy class without modifying any of the existing classes.
public class TrainStrategy : ITravelStrategy{ public void Travel(string destination) { Console.WriteLine("Traveling to " + destination + " by train."); }}
The client code simply sets the new strategy (TrainStrategy) and calls the Travel method without any changes to the existing code.
4. Interchangeability:
Travel strategies can be changed at runtime, allowing for dynamic behavior.
By refactoring to use the Strategy pattern, the code becomes cleaner, more maintainable, and easier to extend.
2. Increased Number of Classes: The Strategy pattern can lead to a proliferation of classes. For each new strategy, you need to create a new class. This can make the system more complex and harder to maintain, especially if there are many different strategies.
3. Lack of Shared State: Since strategies are meant to be interchangeable, they should not share state. This means each strategy must maintain its own state, even if some state could be shared. This can lead to duplicated code and increased memory usage if the state cannot be easily shared.
4. Clients Must Know About Strategies: Clients must be aware of the different strategies and know when to use each one. This can complicate client code and reduce encapsulation, as clients need to manage the strategy selection logic.

Today I showed one of the real examples in everyday programming and how it is possible to use the Strategy Design pattern to solve that problem.
Although the solution is good, it is not realistic to use the same pattern in every situation.
That is why it is necessary to know the pros & cons of the pattern in a certain situation and make a decision.
Would you use another pattern for this? What about Chain Responsibility Pattern? Write to me.
That's all from me today.
Want more design patterns with real-world examples? My ebook Design Patterns that Deliver covers 5 essential patterns (Builder, Decorator, Strategy, Adapter, Mediator) with hands-on C# code you can use right away. Or try a free chapter on the Builder Pattern first.
Stop arguing about code style. In this course you get a production-proven setup with analyzers, CI quality gates, and architecture tests — the exact system I use in real projects. Join here.
Not sure yet? Grab the free Starter Kit — a drop-in setup with the essentials from Module 01.
Design Patterns that Deliver — Solve real problems with 5 battle-tested patterns (Builder, Decorator, Strategy, Adapter, Mediator) using practical, real-world examples. Trusted by 650+ developers.
Just getting started? Design Patterns Simplified covers 10 essential patterns in a beginner-friendly, 30-page guide for just $9.95.
Every Monday morning, I share 1 actionable tip on C#, .NET & Architecture that you can use right away. Join here.
Join 20,000+ subscribers who mass-improve their .NET skills with actionable tips on C#, Architecture & Best Practices.
Subscribe to the TheCodeMan.net and be among the 20,000+ subscribers gaining practical tips and resources to enhance your .NET expertise.