I am often asked where to start if you are learning design patterns. A Strategy pattern is a perfect way to start. It is probably the most straightforward pattern. If I think about it in only a few words, I would say that it is about using composition rather than inheritance.

But, let’s consider the basic logic of the Strategy pattern. This behavior is typical in everyday programming that we do not even think about using this design pattern. When we implement an interface, we are already using the strategy pattern. The basic idea is that we are defining an interface, and we are implementing it separately.

What is the Strategy pattern?

So what is the gist of a strategy pattern? There are systems whose behavior can be determined using a concrete algorithm from a specific family. All this family’s algorithms are related: they are designed to solve common problems, have the same interface for use, and differ only in implementation (behavior). Having configured the program for the preferable algorithm (by choosing a strategy), the user receives the desired result. For example, an application designed to compress files may use one of the available algorithms: ZIP, ARJ, or RAR.

The object-oriented design of such a program might be built using the idea of polymorphism. As a result, we get a group of related classes with a familiar interface and different algorithm’ implementations.

What problems Strategy pattern solves?

Let’s try to think about the purpose of using a strategy pattern. You may get advantages if:

  • The task includes a choice of several algorithms that can be easily switched according to the conditions.
  • It is appropriate to switch the behavior at the stage of program execution.
  • A class operating a particular function doesn’t need to know anything about its implementation.

UML

In appropriate form, the Strategy pattern can be expressed as the following UML chart:

Strategy pattern UML diagram

Examples and implementation details

public interface IStrategy
{
    void Algorithm();
}
 
public class ConcreteStrategy1 : IStrategy
{
    public void Algorithm()
    {}
}
 
public class ConcreteStrategy2 : IStrategy
{
    public void Algorithm()
    {}
}
 
public class Context
{
    public IStrategy ContextStrategy { get; set; }
 
    public Context(IStrategy strategy)
    {
        ContextStrategy = strategy;
    }
 
    public void ExecuteAlgorithm()
    {
        ContextStrategy.Algorithm();
    }
}

As can be seen from the UML chart, there are the following segments:

The IStrategy interface, which determines the Algorithm method. It is a common interface for all algorithms that implement it. An abstract class could also be used here instead of an interface.

The ConcreteStrategy1 and ConcreteStrategy classes implement the IStrategy interface by providing their version of the Algorithm method. There can be many such implementations.

The Context class holds an instance to an object implementing the IStrategy interface.

In this case, the object is assigned in the ContextStrategy property. However, it would also be possible to define a private variable and use a special method for the dynamic setting.

In nowadays, programming strategy implementations would be passed through the Context constructor using IoC Container.

Now let’s look at a concrete example. There are various passenger cars, and the energy sources are different: electricity, gasoline, gas, and others. There are hybrid cars. Generally speaking, they are much the same and vary mainly in the type of energy source. Also, we can switch the energy source used by modifying the car. And in this matter, it is an excellent opportunity to apply the pattern strategy:

public class Program
{
    static void Main(string[] args)
    {
        Car auto = new Car(4, "Volvo", new PetrolMove());
        auto.Move();
        auto.Movable = new ElectricMove();
        auto.Move();
 
        Console.ReadLine();
    }
}
public interface IMovable
{
    void Move();
}
 
public class PetrolMove : IMovable
{
    public void Move()
    {
        Console.WriteLine("moving on gasoline");
    }
}
 
public class ElectricMove : IMovable
{
    public void Move()
    {
        Console.WriteLine("moving on electricity");
    }
}

public class Car
{
    private int passengers; // number of passengers
    private string model; // automobile model
    private IMovable Movable { get; set; }
 
    public Car(int num, string model, IMovable mov)
    {
        this.passengers = num;
        this.model = model;
        this.Movable = mov;
    }
   
    public void Move()
    {
        Movable.Move();
    }
}

In such a case, the IStrategy is the IMovable interface that defines the Move method. And the family of algorithms implementing this interface is represented by the ElectricMove plus PetroleMove classes. And these algorithmic rules are used by the Car class.

Let’s summarize

The system is easier to modify since the family of algorithms has been moved to separate classes.

The Strategy pattern provides the ability to replace one algorithm with another during program execution.

The strategy allows you to hide the implementation of algorithms from the client (by the client, we mean the Conext).

Of course, there are drawbacks to which you should pay attention.

The user should be aware of the specifics of all algorithmic rules in the system settings login.

The number of classes in a system built using the Strategy pattern is increasing. So more support is needed.

Thus, what are the stages of implementation?

Identify an algorithm that is subject to frequent changes. Also suitable is an algorithm that has several variations that are selected during program execution.

Create a strategy interface that describes this algorithm. It must be common to all variants of the Algorithm.

Place variations of the algorithm in the custom classes that implement this interface.

In the context class, create a field to store an instance to the current strategy object and a method to change it (if needed).

Make sure that the context works with this object only through the common strategy interface.

Context clients must submit an appropriate strategy object to it when they want the context to behave in a certain way.

Write A Comment