These days design patterns are divided into 4 categories;
Creational: Creational design patterns provide various object creation mechanisms, which increase flexibility and reuse of existing code e.g. Singleton, Factory.
Behavioral : Behavioral design patterns are concerned with algorithms and the assignment of responsibilities between objects e.g. Strategy.
Structural: Structural design patterns explain how to assemble objects and classes into larger structures while keeping these structures flexible and efficient e.g. Façade, Decorator.
Concurrency: This is a fairly new category since it classifies any pattern that is involved in multi-threading algorithms. These are not in the GoF book!
Within these categories, there are probably too many patterns to write about. In this series, we’ll keep it to the essentials. Make sure to check out all related blog post below!
Strategy Pattern
Abstract Factory Pattern
Command Pattern
Composite Pattern
Decorator Pattern
Iterator Pattern
Visitor Pattern (?)
Bridge Pattern (?)
Strategy Pattern
Let’s take a closer look at perhaps the most famous design pattern: “strategy”. This pattern belongs to the behavioral design patterns category. The strategy design pattern provides the user a way to change the behavior of a class without extending it. This pattern can be recognized by a method that lets nested object do the actual work, as well as the setter that allows replacing that object with a different one. It’s heavily based on one of the SOLID principles (open close).
In this blog post we’ll have a look at the following topics:
Definition
Architecture
Code sample
References
1. Definition
“Strategy Pattern defines a family of algorithms, encapsulates each algorithm and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use them. “
Let’s take a closer look at the different things mentioned above:
“a family of algorithms”… is just a bunch of code structured in classes, but it sounds fancy, right?
“encapsulates each algorithm”… this process binds each function(algorithm) into a single unit(concrete function) to prevent alteration of code accidentally from outside the function itself. Think of the encapsulation principle in OO programming.
“interchangeable”… A List<IStrategy> can contain an instance of each type of algorithm that the user specifies. This is a great example of runtime polymorphism or late binding. It enables the user to work with a family of algorithms in a uniform way.
“vary independently”… The concrete strategy classes have to implement the strategy interface (or abstract class) but can also contain other properties and functions.
When you find yourself writing a lot of big if- or switch-statements to have classes do something different or when you have a lot of similar classes that only differ in the way they execute some behavior, you may have a candidate for a strategy pattern. Some examples can be saving files in different formats, running various algorithms or file compression.
2. Architecture
The architecture consists of a context class which is holding a reference to the strategy. The strategy, which can be defined as an abstract class or an interface is implemented by each of the concrete strategies. The strategy to use can be set/changed at runtime and or when the context is created. To do so, notice the “SetPriceCalculatingStrategy(strategy)”. This is where all the magic happens. Imagine that there was only one place to calculate the order total and one day, the bartender calls us and says that during the weekend, prices are 20% higher in comparison to weekdays. Each time he changes the calculate algorithm, we need to change the calculation method as well. That would be a violation of the open close principle (SOLID). To prevent this, it’s best to use the strategy pattern and to create a new concrete strategy class for each different algorithm. This way you do not have to change the existing code and you can add changes to the current code.
Participants
Strategy: this abstract class or interface defines a common Interface to all supported strategy algorithms.
ConcreteStrategy: these objects implement the strategy algorithm.
Context: this class contains a reference to the strategy object to call its algorithm.
3. Code Sample
Below I have translated this pattern into understandable code language.
public class Item
{
public string Name { get; }
public double Price { get; }
public Item (string name, double price)
{
Name = name;
Price = price;
}
};
Here we created a common item class that represents each item that can be sold. Below we can find the declaration of the common strategy interface and three different algorithms to calculate the order total. They all implement the common strategy interface.
// This class defines a common Interface to all supported strategy algorithms
public interface IPriceCalculatingStrategy
{
public double CalculateOrderTotal(IEnumerable items);
}
// A Concrete Strategy class
public class WeekdayPriceCalculatingStrategy : IPriceCalculatingStrategy
{
public double CalculateOrderTotal(IEnumerable items)
{
return items.Select(r => r.Price).Sum();
}
}
// A Concrete Strategy class
public class WeekendPriceCalculatingStrategy : IPriceCalculatingStrategy
{
public double CalculateOrderTotal(IEnumerable items)
{
return items.Select(r => r.Price * 1.2).Sum();
}
}
// A Concrete Strategy class
public class PartyPriceCalculatingStrategy : IPriceCalculatingStrategy
{
public double CalculateOrderTotal(IEnumerable items)
{
return items.Select(r => r.Price * 0.8).Sum();
}
};
Next, we create the context class that contains a reference to our strategy. It also contains the method to set the strategy to use and the method to calculate the order total based on that strategy.
// Context Class containing a reference to the strategy
public class Order
{
IEnumerable _items;
IPriceCalculatingStrategy _priceCalculatingStrategy;
public Order(IEnumerable items)
{
_items = items;
}
public void SetPriceCalculatingStrategy(IPriceCalculatingStrategy strategy)
{
_priceCalculatingStrategy = strategy;
}
public double CalculateOrderTotal()
{
return _priceCalculatingStrategy.CalculateOrderTotal(_items);
}
};
In our main program, we create an order containing a couple of items. We calculate the order total each time we set or change the strategy. In the console window, we can track the different outputs that match the strategy used at that time.
class Program
{
static void Main(string[] args)
{
List items = new List
{
new Item { Name = "Cola", Price = 2},
new Item { Name = "Fanta", Price = 3},
};
var order = new Order(items);
order.SetPriceCalculatingStrategy(new WeekdayPriceCalculatingStrategy());
Console.WriteLine($"Total order price on weekdays is {order.CalculateOrderTotal()}");
order.SetPriceCalculatingStrategy(new WeekendPriceCalculatingStrategy());
Console.WriteLine($"Total order price on weekend days is {order.CalculateOrderTotal()}");
order.SetPriceCalculatingStrategy(new PartyPriceCalculatingStrategy());
Console.WriteLine($"Total order price on party is {order.CalculateOrderTotal()}");
Console.ReadLine();
}
};