Design Patterns : Strategy Pattern

If there is one thing that is constant in every software development projects, then it has to be CHANGE. Requirements evolve overtime and you might end up with fairly different set of requirements that you initially started of. As developers one has to be aware of the possibility of change and design applications that are flexible to changes. You do not want your application to be fragile and rigid, that would be the least you would desire of your software design.

Let us consider the hypothetical situation where you were asked to develop a module that would sort a collection. The customers mentions he would like the module clients to be provided with ability to choose from the QuickSort and BubbleSort. To begin with, you thought you would depend on inheritance and create a SortBase class, which would be implemented include the abstract Sort method, and the implementation of common methods.

public class SortBase
{

    public IEnumerable<int> Read(string fileName)
    {

    }

    public abstract IEnumerable<int> Sort();

    public void Display()
    {

    }

}

public class QuickSort:SortBase
{

}

public class BubbleSort:SortBase
{

}

Just as you begin development, your Boss walks in and says – “Hey, you know what, I just heard from the stakerholders. They would like to add options to read the data from different format like JSON, CSV. And they would like to support more sorting algorithms as well.”.

If you were to use the earlier approach of inheritance, you would require a sub class for each combination of input type-sorting algorithm. This might get quite ugly soon and is difficult to maintain. Perhaps, inheritance wasn’t the right solution here, may be you should prefer composition over inheritance here.

At this point, you realize the input methods and sorting algorithms are prone to changes. Hence these needs to be isolated and encapsulated. If you could achieve so, then the parts which could vary, could change independently without affecting the crux of the application logic (which doesn’t change). At this point, you remember your friend talking about Strategy Pattern and thinks this might be a good use to apply it.

Strategy is a behavioral design pattern that lets you define a family of algorithms, put each of them into a separate class, and make their objects interchangeable.

Let us look at the class diagram and see how does this different from the old solution.

As you can observe, with this design you could now add further behaviors in system without affecting the existing code. This is exactly what you would require for accommodating the changes you are expecting in the Input/Sorting algorithms.

Let us now go ahead and write our implementation using the Strategy pattern.

public interface IProduct<T>
{
    ISortAlgorithm<T> SortingAlgorithm { get; set; }
    IRead<T> Reader { get; set; }
    void Display(IEnumerable<T> data);
    IEnumerable<T> Read(string fileName);
    IEnumerable<T> Sort(IEnumerable<T> data);
}

public class Product<T> : IProduct<T>
{
    public ISortAlgorithm<T> SortingAlgorithm { get; set; }
    public IRead<T> Reader { get; set; }

    public IEnumerable<T> Sort(IEnumerable<T> data)
    {
        return SortingAlgorithm.Sort(data);
    }
    public IEnumerable<T> Read(string fileName)
    {
        return Reader.ReadData(fileName);
    }

    public void Display(IEnumerable<T> data)
    {
        Console.WriteLine("Displaying Data");
    }
}

The Product Class, now has two properties, SortingAlgorithm, and Reader of type ISortingAlgorithm<T> and IRead<T>. The Read and Sort methods makes use of these properties to do their operation. However, both methods DO NOT which concrete implementation of the ISortingAlgorithm<T> and IRead<T> are being used.

public interface IRead<T>
{
    IEnumerable<T> ReadData(string fileName);
}

public class CsvReader<T> : IRead<T>
{
    public IEnumerable<T> ReadData(string fileName)
    {
        Console.WriteLine($"Reading using {nameof(CsvReader<T>)}");
        return default;
    }
}

public class JsonReader<T> : IRead<T>
{
    public IEnumerable<T> ReadData(string fileName)
    {
        Console.WriteLine($"Reading using {nameof(JsonReader<T>)}");
        return default;
    }
}


public interface ISortAlgorithm<T>
{
    IEnumerable<T> Sort(IEnumerable<T> dataCollection);
}
public class BubbleSort<T> : ISortAlgorithm<T>
{
    public IEnumerable<T> Sort(IEnumerable<T> dataCollection)
    {
        Console.WriteLine($"Sorting {nameof(BubbleSort<T>)}");
        return default;
    }
}

public class QuickSort<T> : ISortAlgorithm<T>
{
    public IEnumerable<T> Sort(IEnumerable<T> dataCollection)
    {
        Console.WriteLine($"Sorting {nameof(QuickSort<T>)}");
        return default;
    }
}

At run time, the user could replace any sub classes of the contract for the properties, without the Product needing to be aware of the internals of their working.

static void Main(string[] args)
{
    var productInstance = new Product<int>();
    productInstance.SortingAlgorithm = new QuickSort<int>();
    productInstance.Reader = new JsonReader<int>();
    Console.WriteLine("Product Initialized and Algorithm assigned");

    Execute(productInstance,default);

    productInstance = new Product<int>();
    productInstance.SortingAlgorithm = new BubbleSort<int>();
    productInstance.Reader = new XmlReader<int>();
    Console.WriteLine("Product Initialized and Algorithm assigned");

    Execute(productInstance, default);

    productInstance = new Product<int>();
    productInstance.SortingAlgorithm = new BucketSort<int>();
    productInstance.Reader = new CsvReader<int>();
    Console.WriteLine("Product Initialized and Algorithm assigned");

    Execute(productInstance, default);

    Console.ReadLine();
}

public static void Execute(IProduct<int> product,string fileName)
{
    Console.WriteLine("Executing Client Code");
    var data = product.Read(fileName);
    var sortedData = product.Sort(data);
    product.Display(sortedData);
    Console.WriteLine("Completed Execution");
    Console.WriteLine();
}

Complete collection of articles on Design Patterns can be found here

5 thoughts on “Design Patterns : Strategy Pattern

  1. The Strategy is injected directly in the client, which invokes it.

    Yes , it is, but it means the IF-ELSE was moved up to the injector.
    This looks more like DI pattern, than Strategy.

    Like

Leave a comment