Example of the Strategy Pattern
Tags: csharp, designpatterns, dotnet
Published: 2020-02-23 13:18:51

In this post I explain the strategy design pattern and show an example of applying it to import data from different data sources. The strategy pattern is a behavioral design pattern that allows different implementations to be used interchangeably at runtime. An application can select the appropriate method to perform a task.  

![strategy pattern diagram](https://dev-to-uploads.s3.amazonaws.com/i/p0itfpuk1vcjwv7ktats.png)  

The example application lets the user import music data from a csv file source or json file source. Depending on the file type, the import strategy is chosen. The example application is part of my [design patterns repository on GitHub.](https://github.com/cwl157/design-patterns) It requires [Visual Studio 2019](https://visualstudio.microsoft.com/downloads/) and [dotnet core 3.1.](https://dotnet.microsoft.com/download)  

![app display](https://dev-to-uploads.s3.amazonaws.com/i/zoh18o5a9l2e1yzcegm3.PNG)  

### Implementing the Pattern

To implement the strategy pattern we need an interface, a couple of implementations of that interface, a data object to store the imported data and a simple user interface. The object to store the imported album data is called `Album` and the code is below.
```csharp
public class Album
{
    public string Artist { get; set; }
    public string Title { get; set; }
    public int NumberOfTracks { get; set; }
    public int ReleaseYear { get; set; }
}
```
The interface contains one method to import a collection of albums.
```csharp
public interface IImportStrategy
{
    IEnumerable<Album> ImportAlbums(string filePath);
}
```
There are two implementations of the interface. One for csv formatted files and one for json formatted files. Both are below.
```csharp
public class CsvStrategy : IImportStrategy
{
    public IEnumerable<Album> ImportAlbums(string filePath)
    {
        string[] lines = File.ReadAllLines(filePath);
        List<Album> result = new List<Album>();
        for (int i = 1; i < lines.Length; i++)
        {
            string l = lines[i];
            string[] parsedLine = l.Split(',');
            result.Add(new Album
            {
                Artist = parsedLine[0],
                Title = parsedLine[1],
                ReleaseYear = int.Parse(parsedLine[2]),
                NumberOfTracks = int.Parse(parsedLine[3])
            });
        }

        return result;
    }
}

public class JsonStrategy : IImportStrategy
{
    public IEnumerable<Album> ImportAlbums(string filePath)
    {
        JsonSerializerOptions options = new JsonSerializerOptions()
        {
            PropertyNameCaseInsensitive = true
        };
        string content = File.ReadAllText(filePath);
        List<Album> result = JsonSerializer.Deserialize<List<Album>>(content, options);
        return result;
    }
}
```
The code above is the basis of the pattern, an interface that defines a method and multiple implementations of that method. The calling code can decide which strategy it needs to use and isn't tied to one specific implementation. We're also going to create a separate class whose task it is to pick which strategy to use. This is a static class called `ImportStrategyPicker.` It contains one method that takes the fileType and returns the correct `IImportStrategy` implementation.
```csharp
public static class ImportStrategyPicker
{
    public static IImportStrategy Select(string fileType)
    {
        if (fileType == ".csv")
        {
            return new CsvStrategy();
        }
        else if (fileType == ".json")
        {
            return new JsonStrategy();
        }

        throw new ApplicationException("No strategy found for file type: " + fileType);
    }
}
```
This can be called directly from the client code that executes the strategy. This would mean every time a strategy is required, the picker would need to be called first to find one. Sometimes finding the correct strategy can be a lot of work and it makes sense to store the strategy in an object so it can be used multiple times. This example contains the `MusicManager` class for this purpose. It contains the Strategy used for import and the list of albums imported.
```csharp
public class MusicManager
{
    public List<Album> Albums { get; private set; }
    public IImportStrategy Importer { get; set; }

    public MusicManager()
    {
        Albums = new List<Album>();
    }
}
```
The code to pick a strategy based on file type is below. I'm not going to show the entire main method, just the parts that selects and executes the strategy. This can be used in multiple ways in applications.
```csharp
Console.Write("File path (csv or json): ");
string filepath = Console.ReadLine();
string extension = Path.GetExtension(filepath);
manager.Importer = ImportStrategyPicker.Select(extension.ToLower());
var albums = manager.Importer.ImportAlbums(filepath);
```
At this point, the albums are imported from the specified file using either the csv or json import strategy. The full sample application source code is available on [GitHub.](https://github.com/cwl157/design-patterns) It also includes sample data files.  

### Conclusion

In conclusion, we looked at the strategy pattern in this post. The strategy pattern allows an application to pick the appropriate strategy at runtime to perform a task. The example application can import a list of albums from either a csv or json file source.