Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

An Absolute Beginner's Tutorial for Understanding and Implementing Strategy Pattern in C#

4.86/5 (29 votes)
17 Jan 2016CPOL5 min read 23.9K   231  
The aim of this article is to understand the basics of the Strategy pattern.

Introduction

The aim of this article is to understand the basics of the Strategy pattern and try to see when it can be useful and have a rudimentary implementation for better understanding. This is no way to implement the strategy pattern in real world applications and the example taken is also far from real. The idea of this this article is to illustrate the concept of strategy pattern only.

Background

There are many scenarios in application development where there are multiple ways of doing the same operation. We want our application to have the possibility of using all these ways of performing the operations. An example could be payment on ecommerce portals, I could choose to pay using netbanking, I could choose to use a credit card, or I can even choose PayPal for making the payment. All these are ways of performing the same operations. Though every choice will have to follow a different application logic, i.e. separate code.

One more example where we could have the same operation in multiple ways is sorting. I could sort a sequence using any one of the sorting algorithms. So when I have a situation when I want to develop the application in such a way that for any operation the user could choose one of many available options, then perhaps that is the right place to incorporate the Strategy pattern.

The philosophy of the Strategy pattern is to have multiple strategies for doing some operation and let the user choose (or some algorithm based on input data) the appropriate strategy to carry out the operation. GoF defines the Strategy pattern as “Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.”

Image 1

Let us try to understand each component of this class diagram.

  • Strategy: This is the interface common to all algorithms. Context uses this interface to perform the operations.
  • ConcreteStrategy: This is the class that implements the actual algorithm.
  • Context: This is the client application that performs the decision making for which strategy should be used and uses the Strategy interface (which is referring to a ConcreteStrategy object) to perform the operations.

Using the code

So to understand these concepts, let us work on a toy application for converting audio files from Wav to MP3. Here the user will have an option of selecting the source .wav file and then select the quality of the target MP3 file to be created. The user interface will provide the user with three quality values for the target file.

In our code what we will do is that we will create multiple strategies each that we will be using are to define the quality of the output. We are implementing all these separate strategies so that conversion code will remain unaffected by the code that is specific to dealing with the quality of the output. The actual code that is doing the conversion will be independent of the implementation details of these different strategies and will work in the same fashion for any selected strategy or even when the new strategies are added.

Note: This is not a real conversion application, only a dummy application but perhaps the real application can be developed on the same lines. Also, the choice of the application and design is purely to demonstrate the Strategy pattern in action (could be a bad design if we take the holistic view).

So let us first write the interface IWavToMp3ConvertionStrategy that will be implemented by all Concrete Strategies.

C#
interface IWavToMp3ConvertionStrategy
{
	void Convert();
}

Once we have the interface ready, we can write the Concrete Strategy classes.

C#
//Strategy class for low quality conversion
class LowQualityConversionStrategy : IWavToMp3ConvertionStrategy
{
	public void Convert()
	{
		Console.WriteLine("Low quality conversion performed");
	}
}

//Strategy class for average quality conversion
class AverageQualityConversionStrategy : IWavToMp3ConvertionStrategy
{
	public void Convert()
	{
		Console.WriteLine("Average quality conversion performed");
	}
}

//Strategy class for high quality conversion
class HighQualityConversionStrategy : IWavToMp3ConvertionStrategy
{
	public void Convert()
	{
		Console.WriteLine("High quality conversion performed");
	}
}

We have our concrete classes containing the logic that is different among all strategies. All the logic that is common to all the conversion feature will be present in the class that will be choosing the strategy for the actual conversion i.e. the context class in the above diagram. So let us write the Context class i.e WavToMP3Convertor that will be using the IWavToMp3ConvertionStrategy interface to perform the conversion.

C#
public class WavToMP3Convertor
{
	AudioFile m_fileData = null;
	IWavToMp3ConvertionStrategy m_Convertor = null;

	public WavToMP3Convertor(AudioFile fileData)
	{
		m_fileData = fileData;            
	}

	public void Convert(IWavToMp3ConvertionStrategy convertor)
	{
		m_Convertor = convertor;
		m_Convertor.Convert();            
	}
}

What is happening in this class is that the presentation layer is passing the actual file data to this class. then when the conversion will be requested a concrete strategy instance will also be passed to this class so that this class can use the passed strategy for the conversion.

The important thing to note in this code is that this code will remain unaffected even if we add more strategies. Also, if we were to create a SDK/DLL, the user will have options to create their own strategies and simply pass them to this class to use their custom strategies.

Finally we will have our presentation layer which will let the user decide the converstion strategy and pass the appropriate strategy to the conversion class.

C#
static void Main(string[] args)
{
	IWavToMp3ConvertionStrategy selectedStrategy = null;

	Console.WriteLine("Assuming the file for conversion has been selected already");
	AudioFile file = new AudioFile { Title = "Sample File" };

	// Let us try to emulate the selection of quality here
	Console.WriteLine("Enter the type of output \n1. Low Quality\n2. Average Quality\n3. High Quality");
				
	int choice = Console.Read();

	// Now based on the users' choice lets go ahead and select strategy to convert the file
	if (choice == '1')
	{
		selectedStrategy = new LowQualityConversionStrategy();                
	}
	else if (choice == '2')
	{
		selectedStrategy = new AverageQualityConversionStrategy();                
	}
	else if (choice == '3')
	{
		selectedStrategy = new HighQualityConversionStrategy();                
	}

	// Now the code which is doing the conversion. this code beed 
	// not be changes even if we implement more strategies
	if (selectedStrategy != null)
	{
		WavToMP3Convertor convertor = new WavToMP3Convertor(file);
		convertor.Convert(selectedStrategy);
	}
}

Note: The selection of strategy is being done in the presentation layer in this sample application. This is rather a very contrived way of creating the concrete strategies. In real world the concrete strategies will be created either using a factory or a service locator based on the user's selection on UI.

Now when we run the application we can see that the file conversion will happen based on the user selected options and the right strategy will be used. Now let us try to compare our code with the GoF class diagram.

Image 2

So we can see that IWavToMp3ConvertionStrategy is our strategy interface, WavToMP3Convertor is our context class and LowQualityConversionStrategyAverageQualityConversionStrategy and HighQualityConversionStrategy are our concrete strategies. The important thing to note is that the Context class i.e. WavToMP3Convertor will remain unaffected by the concrete strategies and adding/removing strategies will not have any effect on this class. 

Points of interest

In this article we have looked at the basics of strategy pattern and how we can use this pattern to decouple the client code with the actual implementation. We have used a rather contrived example just to demonstrate this pattern. The creation of concrete strategy was also not be done using a switch statement but here the intention was to show how the Context class remain unaffected by the choice of strategies and thus this simple way of creating concrete strategy. This article has been written from an absolute beginner's perspective. I hope this has been informative.

History

  • January 18, 2016 - First version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)