Strategy pattern is one of the behavioral design patterns. This group of patterns regard communication between objects. They describe the way objects and classes interact and divide responsibilities among themselves.
Intent
The intent of the Strategy Pattern is to encapsulate related algorithms and make them callable through a common interface. This pattern decouples algorithm implementation into independent classes so they might evolve separately from the classes that actually invoke them through the interface. Such classes maintain a single purpose and fulfill the Open/Closed Principle. Use this pattern to get rid of your switch or if statements.
Example
Take a look at the CalculateShippingCost method below. It calculates the cost of shipping the order based on order’s shipping method.
public class ShippingCostCalculatorService
{
public double CalculateShippingCost(Order order)
{
switch (order.ShippingMethod)
{
case Order.ShippingOptions.DHL:
return CalculateForDHL(order);
case Order.ShippingOptions.UPS:
return CalculateForUPS(order);
case Order.ShippingOptions.PostOffice:
return CalculateForPostOffice(order);
default:
throw new UnknownOrderShippingMethodException();
}
}
double CalculateForPostOffice(Order order)
{
return 3.00d;
}
double CalculateForUPS(Order order)
{
return 4.25d;
}
double CalculateForDHL(Order order)
{
return 5.00d;
}
}
Such code is of course not acceptable. The code smell is the switch statement that determines the choice which calculation the method should perform. It breaks the Open/Closed Principle – it is not closed for modification. Every time a new shipping method needs to be added, CalculateShippingCost method will need to be reopen and modified.
What to do?
- Create separate classes (strategies) for each calculation
- Use a common interface for each strategy
UML Model
Context class is the class that will actually do the work. Instead of making the DoSomething method choose its calculations on the basis of switch or if statement, the Context class takes a concrete instance of a class that implements the Strategy interface. IStrategy declares a method that will be the one called by the Context class to perform the calculation. When we call Context.DoSomething, it uses the strategy that it was given on its constructor to call PerformWork on the concrete class.

In case of the above example, the ShippingCostCalculatorService will be our Context.

Strategy implementation
ShippingCostCalculatorService takes an IShippingCostStrategy object as its constructor argument.
public class ShippingCostCalculatorService
{
readonly IShippingCostStrategy shippingCostStrategy;
public ShippingCostCalculatorService(IShippingCostStrategy shippingCostStrategy)
{
this.shippingCostStrategy = shippingCostStrategy;
}
public double CalculateShippingCost(Order order)
{
return shippingCostStrategy.Calculate(order);
}
}
IShippingCostStrategy declares a Calculate method which wants an order to be passed in so that it can use the information in the order to calculate the shipping cost. CalculateShippingCost method – which also takes an order as its argument – invokes the concrete classes Calculate method.
public interface IShippingCostStrategy
{
double Calculate(Order order);
}
public class PostOfficeShippingCostStrategy : IShippingCostStrategy
{
public double Calculate(Order order)
{
return 3.00d;
}
}
public class UPSShippingCostStrategy : IShippingCostStrategy
{
public double Calculate(Order order)
{
return 4.25d;
}
}
public class DHLShippingCostStrategy : IShippingCostStrategy
{
public double Calculate(Order order)
{
return 5.00d;
}
}
Consequences
- With the strategy pattern implemented we fulfill the Open/Closed principle, as the Context class does not have to be modified when a new strategy is added.
- Tests can now be written for individual strategy classes. Each strategy can also be mocked when testing the Context class.
- Our strategies really have to be self-sustaining – they cannot use the local members of the Context class.
Variations
- Func, delegates
As a variation of the strategy pattern, the strategy can be represented by a delegate rather than an IStrategy object. - Passing strategy to a method rather than the constructor
Rather than passing the strategy as part of the constructor, we can also inject the strategy on the method call itself.
Let’s jump into example of another implementation of the strategy pattern. We have three different delegates. The delegate for DHLStrategy is declared as a method. UpsStrategy has an anonymous delegate and an anonymous delegate declared through a Lambda expression is for the postOfficeStrategy.
public class Program
{
public static void Main(string[] args)
{
Func<Order, double> dhlStrategy = CalcForDHL;
Func<Order, double> upsStrategy = delegate(Order order) { return 4.00d; };
Func<Order, double> postOfficeStrategy = order => 3.25d;
Order theOrder = new Order();
var calculatorService = new ShippingCostCalculatorService();
Console.WriteLine($"DHL Shipping Cost: {calculatorService.CalculateShippingCost(theOrder, dhlStrategy)}");
Console.WriteLine($"UPS Shipping Cost: {calculatorService.CalculateShippingCost(theOrder, upsStrategy)}");
Console.WriteLine($"Post Office Shipping Cost: {calculatorService.CalculateShippingCost(theOrder, postOfficeStrategy)}");
Console.ReadKey();
}
internal static double CalcForDHL(Order arg)
{
return 5.00d;
}
}
We can now pass the order and a strategy to CalculateShippingCost method and print the results.
Before ShippingCostCalculatorService took the IShippingCost object as its constructor’s argument. In this approach, ShippingCostCalculatorService only has the default constructor. The CalculateShippingCost method takes both order and the strategy. The second modification is that the strategy is represented by a delegate rather than an IStrategy object.
public class ShippingCostCalculatorService
{
public double CalculateShippingCost(Order order, Func<order, double> shippingCostStrategy)
{
return shippingCostStrategy(order);
}
}
Personally, I think I would choose the classic constructor injection as passing both order and the strategy as the method’s arguments leads to more procedural rather than object oriented programming. On the other hand, the idea with delegates – that can also be injected in the constructor – seems interesting. What do you think? Comments welcome!
References
https://app.pluralsight.com/library/courses/patterns-library