Hands-On: Strategy Design Pattern in .NET Core 
Tags: csharp, design
Published: 2021-03-11 12:25:21

## About Patterns
Design patterns were introduced in Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides’s seminal work *[Design Patterns: Elements of Reusable Object Oriented Software](https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612)*.

Design patterns provide a high-level language of discourse for programmers to describe a system and to discuss solutions to common problems. The proper and intelligent usage of patterns will guide a developer into designing a system that conforms to well-established prior practices, without stifling innovation.

## The Strategy Pattern
The Strategy Pattern is one of the original patterns described in the famous [Design Patterns](https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612) book. It is a behavioral design pattern that allows us to implement different functionalities in separate classes and make their objects interchangeable. 

Put simply, we have a main Context object that holds a reference towards a Strategy object and delegates it by executing its corresponding functionality.

Without any further ado, let's dive into the Strategy Pattern Design implementation.

This post is organized into the following sections:
- **Strategy Design Pattern Structure**
- **Implementation of the Strategy Pattern**
- **Common usage of the Strategy Pattern**
- **Takeaways and Final Thoughts**

## Strategy Design Pattern Structure
As emphasized above, the strategy design pattern consists of a **Context** object which holds a reference towards the Strategy object. For an exhaustive implementation, we need the **Strategy** object - Interface to define a mechanism for the **Context** object to execute the strategy and the **Concrete Strategies** objects which implement the **Strategy Interface**. 

### The Participants

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/212gpo5o5kjoinl5kco8.png)

- The **Strategy** declares an interface which is implemented by all supported algorithms.
- The **ConcreteStrategy** objects implement the algorithm defined by the Strategy.
- The **Context** maintains a reference to a Strategy object, and uses that reference to invoke the algorithm defined by a particular ConcreteStrategy.

## Implementation of the Strategy Pattern
For this article, let's model this pattern by talking about some different ways to cook food.
### A Delicious Example
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xnmeha84nw3391rkzttl.jpg)

Being an eat-meater, there's often more than one way to cook meats under safe eating temperatures. 

For instance, a meat can be roasted, deep-fried or broiled. Each of these methods will get the item cooked, just via different processes. These processes, in object-oriented code can each be their own class. 

In our example, we will assume that we will ask the user what method they would like to use to cook their food, and then implement that method using the Strategy design pattern.

To start, let's write up the **Strategy** participant, the Interface `ICookStrategyService`.

```csharp
    public interface ICookStrategyService
    {
        void Cook(string food);
    }
```
Each strategy implements the method `Cook()` which indicates how a food item shall be cooked. Let's implement a few of those strategies - also referred to as **ConcreteStrategy** participants.

```csharp
    public class GrillingService : ICookStrategyService
    {
        public void Cook(string food)
        {
            Console.WriteLine($"Cooking {food} by grilling it");
        }
    }

    public class RoastingService : ICookStrategyService
    {
        public void Cook(string food)
        {
            Console.WriteLine($"Cooking {food} by roasting it");
        }
    }

    public class DeepFryingService : ICookStrategyService
    {
        public void Cook(string food)
        {
            Console.WriteLine($"Cooking {food} by deep frying it");
        }
    }
```

In a typical ASP.NET Core application, we shall leverage the built-in dependency injection mechanism to register the services as follows:

```csharp
services.AddTransient<ICookingStrategyService, GrillingService>();
services.AddTransient<ICookingStrategyService, RoastingService >();
services.AddTransient<ICookingStrategyService, DeepFryingService>();
```

We would then inject these instances in the constructor of a class to use these services. However, when we ought to run the application, we will observe that all three parameters are instances of type `DeepFryingService` because `DeepFryingService` has been injected last.

To overcome this limitation, we will use an IEnumerable collection of services to register the services and a delegate to retrieve a specific service instance.

####Use a delegate to retrieve a specific service instance
Consider the following enum that contains integer constants that correspond to the three types `Grill`, `Roast`, and `Fry`.

```csharp
public enum CookingType
{
   Grill,
   Roast,
   Fry
}
```
We then declare a shared delegate:

```csharp
public delegate ICookingStrategyService ServiceResolver(CookingType cookingType);

```

The **ConcreteStrategies** are then registered as `Transient` services in the `Startup.cs` file.

```csharp
services.AddTransient<GrillingService>();
services.AddTransient<RoastingService>();
services.AddTransient<DeepFryingService>();
```

####Return instances based on service type at runtime
The following code snippet shows how we can return instances of the `GrillingService`, `RoastingService`, and `DeepFryingService` classes depending on the service type selected at runtime.

```csharp
services.AddScoped<ServiceResolver>(serviceProvider => key =>
{
      switch (key)
      {
         case CookingType.Grill:
              return serviceProvider.GetRequiredService<GrillingService>();
         case CookingType.Roast:
              return serviceProvider.GetRequiredService<RoastingService>();
         case CookingType.Fry:
              return serviceProvider.GetRequiredService<DeepFryingService>();
          default:
              throw new KeyNotFoundException();
       }
});
```
## When to use Strategy Pattern

- Where different variations for some functionalities in an object and we want to switch from one variation to another in a runtime. Furthermore, if we have business applications that may have many different possible strategies in play, the strategy pattern should be the right choice for us.

## Takeaways and Final Thoughts
The strategy pattern achieves decoupling by encapsulating variable parts of a behavior behind an abstract interface. This abstraction can make working with a family of related algorithms easier, and it lets us postpone introduction of new **ConcreteStrategies**. In addition, the strategy pattern allows for different behaviors to be implemented and tested individually.

Happy Coding! :computer: