Example of the Strategy Pattern

cwl157

Carl Layton

Posted on February 23, 2020

Example of the Strategy Pattern

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

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. It requires Visual Studio 2019 and dotnet core 3.1.

app display

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.

public class Album
{
    public string Artist { get; set; }
    public string Title { get; set; }
    public int NumberOfTracks { get; set; }
    public int ReleaseYear { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

The interface contains one method to import a collection of albums.

public interface IImportStrategy
{
    IEnumerable<Album> ImportAlbums(string filePath);
}
Enter fullscreen mode Exit fullscreen mode

There are two implementations of the interface. One for csv formatted files and one for json formatted files. Both are below.

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

public class MusicManager
{
    public List<Album> Albums { get; private set; }
    public IImportStrategy Importer { get; set; }

    public MusicManager()
    {
        Albums = new List<Album>();
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

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);
Enter fullscreen mode Exit fullscreen mode

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. 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.

💖 💪 🙅 🚩
cwl157
Carl Layton

Posted on February 23, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related