What You Need To Know About The Helpful Strategy Pattern
Tags: csharp, coding, beginners, programming
Published: 2018-01-29 00:22:05

---
title: What You Need To Know About The Helpful Strategy Pattern
published: false
description: 
tags: #csharp #coding #beginners #programming
cover_image: https://thepracticaldev.s3.amazonaws.com/i/6jas9cul840vymfpzx25.png
published: true
canonical_url: https://blog.kylegalbraith.com/2018/01/29/what-you-need-to-know-about-the-helpful-strategy-pattern/
---
I have recently been revisiting various coding patterns while learning new languages. One pattern that is a personal favorite of mine is the strategy pattern. The premise of the strategy pattern is to encapsulate a family of behaviors. When encapsulated we can then select which behavior to use at runtime.

For my example I am going to show the pattern in C#, but this is applicable to a wide array of languages. Let's assume we are creating a piece of code that extracts content from text, pdf, and image files. Our family of behaviors is therefore text extraction. But we want to extract content in different ways based on the type of file.

To get started we define an interface called `ITextExtractor`.

```csharp
public interface ITextExtractor
{
    bool UseExtractor(string fileExtension);
    string[] ExtractContent(string filePath);
}
```

For each type of document we want to extract content from we create a new class that implements `ITextExtractor`. Take note of the `UseExtractor` method as we will use this to select our extractor at runtime. Let's go ahead and create the three text extractors.

### PlainTextExtractor
```csharp
public class PlainTextExtractor : ITextExtractor
{
    public bool UseExtractor(string fileExtension)
    {
        return fileExtension.Equals("txt", StringComparison.OrdinalIgnoreCase);
    }

    public string[] ExtractContent(string filePath)
    {
        // extract the content here...
    }
}
```
`PlainTextExtractor` will return true from `UseExtractor` only if the file extension ends in txt.
### PdfTextExtractor
```csharp
public class PdfTextExtractor : ITextExtractor
{
    public bool UseExtractor(string fileExtension)
    {
        return fileExtension.Equals("pdf", StringComparison.OrdinalIgnoreCase);
    }

    public string[] ExtractContent(string filePath)
    {
        // extract the content here...
    }
}
```
`PdfTextExtractor` will return true from `UseExtractor` only if the file extension ends in pdf.

### ImageTextExtractor
```csharp
public class ImageTextExtractor : ITextExtractor
{
    private string[] _imageTypes = new[] { "png", "jpg", "jpeg", "tiff" };
    public bool UseExtractor(string fileExtension)
    {
        return _imageTypes.Any(it => it.Equals(fileExtension, StringComparison.OrdinalIgnoreCase);
    }

    public string[] ExtractContent(string filePath)
    {
        // extract the content here...
    }
}
```
`ImageTextExtractor ` will return true from `UseExtractor` only if the file extension ends in png, jpg, jpeg, or tiff. There is far more image types than this, but this gives you an idea of what we are after.

### The ¯\_(ツ)_/¯ Approach To Selecting Our Strategy At Runtime
Now we have our various text extractors. When it comes to selecting the appropriate extractor at runtime you often see code written like this.
```csharp
public class RunExtraction
{
    private PlainTextExtractor _plainTextExtractor;
    private PdfTextExtractor _pdfTextExtractor;
    private ImageTextExtractor _imageTextExtractor;

    public RunExtraction(PlainTextExtractor plainTextExtractor,
        PdfTextExtractor  pdfTextExtractor, ImageTextExtractor imageTextExtractor)
    {
        _plainTextExtractor = plainTextExtractor;
        _pdfTextExtractor = pdfTextExtractor;
        _imageTextExtractor = imageTextExtractor;
    }

    public string[] Extract(string filePath, string fileExtension)
    {
        if(_plainTextExtractor.UseExtractor(fileExtension))
        {
            return _plainTextExtractor.ExtractContent(filePath);
        }
        else if(_pdfTextExtractor.UseExtractor(fileExtension))
        {
            return _pdfTextExtractor.ExtractContent(filePath);
        }
        else if(_imageTextExtractor.UseExtractor(fileExtension))
        {
            return _imageTextExtractor.ExtractContent(filePath);
        }
        else
        {
            throw new Exception("unable to extract content");
        }        
    }
}
```
There is technically nothing wrong with this code. But is it the most extensible for the future? What if we had to start extracting content from a Word document? 

First, we would create a new `WordDocumentTextExtractor` class that implements `ITextExtractor`.
```csharp
public class WordDocumentTextExtractor : ITextExtractor
{
    private string[] _docTypes = new[] { "doc", "docx" };
    public bool UseExtractor(string fileExtension)
    {
        return _docTypes.Any(it => it.Equals(fileExtension, StringComparison.OrdinalIgnoreCase);
    }

    public string[] ExtractContent(string filePath)
    {
        // extract the content here...
    }
}
```
We would then have to update the `RunExtraction` class to take the `WordDocumentTextExtractor` in the constructor.
```csharp
private PlainTextExtractor _plainTextExtractor;
private PdfTextExtractor _pdfTextExtractor;
private ImageTextExtractor _imageTextExtractor;
private WordDocumentTextExtractor _wordDocumentTextExtractor;

public RunExtraction(PlainTextExtractor plainTextExtractor,
    PdfTextExtractor  pdfTextExtractor, ImageTextExtractor imageTextExtractor,
    WordDocumentTextExtractor wordDocumentTextExtractor)
{
    _plainTextExtractor = plainTextExtractor;
    _pdfTextExtractor = pdfTextExtractor;
    _imageTextExtractor = imageTextExtractor;
    _wordDocumentTextExtractor = wordDocumentTextExtractor;
}
```
We would then need to add another `else if` statement to check for Word documents.
```csharp
else if(_wordDocumentTextExtractor.UseExtractor(fileExtension))
{
    return _wordDocumentTextExtractor.ExtractContent(filePath);
}
```
This becomes unruly if we are constantly having to extract content from different types of documents. Each time we have to:
1. Add the new text extraction class.
2. Pass the new class into `RunExtraction`.
3. Update the `else if` conditions to detect the new document type.

My anxiety level is rising already. There must be a different approach right?

### The q(❂‿❂)p Approach To Selecting Our Strategy At Runtime
Lucky for us we set ourselves up for success with our strategy pattern. Every text extractor implements the same interface `ITextExtractor`. In that interface we added the method `UseExtractor`. It returns `true` or `false` based on the extensions each extractor supports. We can leverage both of those things to our advantage.

First, we change what is passed into the constructor of `RunExtraction`.
```csharp
private ITextExtractor[] _extractors;

public RunExtraction(ITextExtractor[] extractors)
{
    _extractors = extractors;
}
```
Notice we no longer pass in the concrete classes for each type of extractor. Instead we pass in an array of `ITextExtractor`. This is so that when we want to add a new type of extractor we just add it to the array passed in.

Next, we can change the `Extract` method of `RunExtraction` to no longer use `if` `else if`....`else if`.
```csharp
public string[] Extract(string filePath, string fileExtension)
{
    var extractor = _extractors.FirstOrDefault(e => e.UseExtractor(fileExtension));
    if(extractor != null)
    {
        return extractor.ExtractContent(filePath);
    }
    else
    {
        throw new Exception("unable to extract content");
    }
}
```
Goodbye `else if` and hello extensibility. Our `RunExtract` class can now easily support new document text extractors. Now when we want to add our `WordDocumentTextExtractor` here are the steps we need to complete:
1. Add the new text extraction class.
2. Add the new class to the passed in array of extractors to `RunExtraction`.

### Conclusion
Here we have covered the basics of strategy pattern.
* Define an interface for similar functionality. (like text extraction)
* Create concrete classes that implement that functionality. 
* Use dependency injection and interfaces to seal your logic class from changes. 
* Dynamically select the necessary functionality at runtime. 

Then watch as your code becomes more extensible in the future as different types of concrete classes are added.

### Hungry To Learn Amazon Web Services?
There is a lot of people that are hungry to learn Amazon Web Services. Inspired by this fact I have created a course focused on learning Amazon Web Services by using it. Focusing on the problem of hosting, securing, and delivering static websites. You learn services like S3, API Gateway, CloudFront, Lambda, and WAF by building a solution to the problem.

There is a sea of information out there around AWS. It is easy to get lost and not make any progress in learning. By working through this problem we can cut through the information and speed up your learning. My goal with this book and video course is to share what I have learned with you.

Sound interesting? Check out the landing page to learn more and pick a package that works for you, [here](https://www.kylegalbraith.com/learn-aws/).