Terug naar overzicht

Design Patterns – Strategy Pattern

Door Geert Van Hoef

.NET Core .NET Standard

Nov 2020

Design patterns are used consciously and sometimes unconsciously every day to solve certain recurring problem areas within software development. They are code patterns other developers have noticed they used repeatedly and shared them with the rest of the world. A list of patterns was bundled by the (in)famous Gang of 4 (GoF), outlaws from the olden days, in the book Design Patterns: Elements of Reusable Object-Oriented Software.

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

  1. Strategy: this abstract class or interface defines a common Interface to all supported strategy algorithms.

  2. 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();
  }
};

Console output

4. References

Application Logging – “That warm fuzzy blanket for when production doesn’t behave” Thumb

Door Steven Hillaert

Nov 2023

Application Logging – “That warm fuzzy blanket for when production doesn’t behave”

Whenever I see a codebase that has logs, I feel safe. Because when things start to break, I know I’ll have data to help me fix it.

Enabling (as) the next generation software developers Thumb

Door Ruben Verheyen

Oct 2022

Enabling (as) the next generation software developers

Wij zijn allemaal AllPhi. Ieder van ons maakt deel uit van het grote geheel en kan vanuit z’n eigen positie z’n steentje bijdragen. En wanneer we er bewust ...

AllPhi Culture
.NET MAUI, a bright and shiny new bit of technology Thumb

Door Mathias Peene

Aug 2022

.NET MAUI, a bright and shiny new bit of technology

What’s my next experience with MAUI? I’m probably going to continue playing around with the MonkeyFinder application, just to get to know the framework ...

.NET Core .NET Standard
Cache primary btn default asset Cache primary btn hover asset Cache white btn default asset Cache white btn hover asset