Strategy Pattern with example

Strategy pattern defines a family of algorithms, encapsulates each one of them and makes them interchangeable

  • Family of Algorithms– The definition says that the pattern defines the family of algorithms- it means we have functionality (in these algorithms) which will do the same common thing for our object, but in different ways.
  • Encapsulate each one of them– The pattern would force you to place your algorithms in different classes (encapsulate them). Doing so would help us in selecting the appropriate algorithm for our object.
  • Make them interchangeable – The beauty with strategy pattern is we can select at run time which algorithm we should apply to our object and can replace them with one another

For example we need to develop a simple shipping cost calculation service where the calculation will depend on the type of the carrier: FedEx, UPS, DHL and USPS.

public class Address
{
    public string ContactName { getset; }
    public string AddressLine1 { getset; }
    public string AddressLine2 { getset; }
    public string AddressLine3 { getset; }
    public string City { getset; }
    public string Region { getset; }
    public string Country { getset; }
    public string PostalCode { getset; }
}
 
 
public enum ShippingOptions
{
    UPS = 10,
    FedEx = 20,
    USPS = 30,
    DHL = 40
}
 
public class Order
{
    public ShippingOptions ShippingMethod { getset; }
    public Address Destination { getset; }
    public Address Origin { getset; }
}
 
public class ShippingCostCalculatorService
{
    public double CalculateShippingCost(Order order)
    {
        switch (order.ShippingMethod)
        {
            case ShippingOptions.FedEx:
                return CalculateForFedEx(order);
            case ShippingOptions.UPS:
                return CalculateForUPS(order);
            case ShippingOptions.USPS:
                return CalculateForUSPS(order);
            case ShippingOptions.DHL:
                return CalculateForDHL(order);
            default:
                throw new Exception("Unknown carrier");
 
        }
    }
 
    double CalculateForDHL(Order order)
    {
        return 4.00d;
    }
 
    double CalculateForUSPS(Order order)
    {
        return 3.00d;
    }
 
    double CalculateForUPS(Order order)
    {
        return 4.25d;
    }
 
    double CalculateForFedEx(Order order)
    {
        return 5.00d;
    }
}

It is perfectly reasonable that we may introduce a new carrier in the future, say XYZ. If we pass an order with this shipping method to the CalculateShippingCost method then we’ll get an exception. We’d have to manually extend the switch statement to account for the new shipment type. In case of a new carrier we’d have to come back to this domain service and modify it accordingly. That breaks the Open/Closed principle of SOLID: a class is open for extensions but closed for modifications. In addition, if there’s a change in the implementation of one of the calculation algorithms then again we’d have to come back to this method and modify it. That’s generally not a good practice: if you make a change to one of your classes, then you should not have to go an modify other classes and public methods just to accommodate that change. Methods that calculate the costs are of course ridiculously simple in this demo – in reality there may well be calls to other services, the weight of the package may be checked etc., so the ShippingCostCalculatorService class may grow very large and difficult to maintain. The calculator class becomes bloated with logic belonging to UPS, FedEx, DHL etc, violating the Single Responsibility Principle. The service class is trying to take care of too much. The solution is basically to create a class for each calculation – we can call each implemented calculation a strategy. Each class will need to implement the same interface. If you check the calculation methods in the service class then the following interface will probably fit our needs:

public interface IShippingStrategy
{
    double Calculate(Order order);
}

Next we will implement strategies

public class USPSShippingStrategy : IShippingStrategy
{
    public double Calculate(Order order)
    {
        return 3.00d;
    }
}
 
public class UpsShippingStrategy : IShippingStrategy
{
    public double Calculate(Order order)
    {
        return 4.25d;
    }
}
 
 
public class FedexShippingStrategy : IShippingStrategy
{
    public double Calculate(Order order)
    {
        return 5.00d;
    }
}
 
public class DHLShippingStrategy : IShippingStrategy
{
    public double Calculate(Order order)
    {
        return 4.00d;
    }
}

The cost calculation service is now ready to accept the strategy from the outside. The new and improved service looks as follows:

public class ShippingCostCalculatorServiceWithStrategy
{
    private readonly IShippingStrategy _shippingStrategy;
 
    public ShippingCostCalculatorServiceWithStrategy(IShippingStrategy shippingStrategy)
    {
        _shippingStrategy = shippingStrategy;
    }
 
    public double CalculateShippingCost(Order order)
    {
        return _shippingStrategy.Calculate(order);
    }
}

You can now implement the IShippingStrategy as new carriers come into the picture and the calculation service can continue to function without knowing anything about the concrete strategy classes. The concrete strategy classes are self-containing, they can be tested individually and they can be mocked

Attached is the source code over here

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s