What is Strategy Design Pattern?

What is Strategy Design Pattern?

Let’s say you are implementing an online eCommerce shop checkout page and you have been told to implement a feature that will allow customers to choose one of the multiple shipping methods. Each shipping method can have different logics to calculate shipping charges and there can also be a situation where different developers need to implement different algorithms. It will be a bad idea to write a single class with all the calculations and logic in it. You have to split your code in a way that is easy to maintain by different developers and easy to scale if more shipping methods are added in the future. In this tutorial, I will show you how to use a design pattern called Strategy Pattern to not only implement multiple algorithms independently but also switch from one algorithm to another during runtime.

What is a Strategy Pattern?

The strategy pattern (also known as the policy pattern) is a behavioural design pattern that allows us to implement a family of algorithms (strategies) into separate classes and then switch from one algorithm (strategy) to another during runtime. Using this pattern, developers can isolate the code, internal logic, and the dependencies of various algorithms from the rest of the application code which makes the code easy to maintain and easy to scale. This pattern also allows us to alter the behavior of the application at runtime by switching from one algorithm to another algorithm.

Pros of Strategy Pattern

  1. We can isolate the implementation details of different algorithms from the business logic that uses these algorithms
  2. We can switch from one algorithm to another at runtime
  3. We can introduce or replace algorithms without changing the context

Cons of Strategy Pattern

  1. If you have only a couple of algorithms that rarely change, then you don’t have to overcomplicate your code by introducing new classes and interfaces that come along with this pattern.
  2. The client needs to know the difference between different strategies to select the most suitable one.

Setting Up an ASP.NET Core Demo App

Strategy pattern can be applied in many real-world use cases where we have multiple algorithms to handle such as

  1. An Ecommerce shop checkout page with multiple shipping methods.
  2. An Ecommerce shop checkout page with multiple payment methods.
  3. An application that generates images in multiple formats
  4. An application that authenticates users using Basic, OAuth, and OpenID authentication.

For this tutorial, I will implement a demo eCommerce checkout page that will allow the user to choose one of the shipping methods and I will show you how a Strategy pattern can help us in switching from one shipping method to another shipping method dynamically.

Create a new ASP.NET Core MVC 5 Web API Application and create the following?ShippingMethod?class.

ShippingMethod.cs

? ? public class ShippingMethod
? ? {
? ? ? ? public int Id { get; set; }
? ? ? ? public string Name { get; set; }
? ? }        

Next, create the following CheckoutModel class that will help us in creating a demo checkout page.

CheckoutModel.cs

? ? public class CheckoutModel
? ? {
? ? ? ? public int SelectedMethod { get; set; }
? ? ? ? public decimal OrderTotal { get; set; }
? ? ? ? public decimal FinalTotal { get; set; }
? ? }        

Open the?HomeController?and add the following private method in the controller. The?GetShippingMethods?method will return a list of shipping methods for our demo app.?

? ? ? ? private List<ShippingMethod> GetShippingMethods()
? ? ? ? {
? ? ? ? ? ? return new List<ShippingMethod>()
? ? ? ? ? ? {
? ? ? ? ? ? ? ? new ShippingMethod()
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? Id = 1,
? ? ? ? ? ? ? ? ? ? Name="Free Shipping ($0.00)"
? ? ? ? ? ? ? ? },
? ? ? ? ? ? ? ? new ShippingMethod() {
? ? ? ? ? ? ? ? ? ? Id = 2,
? ? ? ? ? ? ? ? ? ? Name="Local Shipping ($10.00)"
? ? ? ? ? ? ? ? },
? ? ? ? ? ? ? ? new ShippingMethod() {
? ? ? ? ? ? ? ? ? ? Id = 3,
? ? ? ? ? ? ? ? ? ? Name="Worldwide Shipping ($50.00)"
? ? ? ? ? ? ? ? }
? ? ? ? ? ? };
? ? ? ? }        

Call?GetShippingMethods method inside the Index action of the?HomeController?as shown in the code snippet below. We are simply returning the model to the API.

? ? ? ? [HttpGet]
? ? ? ? [Route("GetShippingMethods")]
? ? ? ? public IActionResult Get()
? ? ? ? {
? ? ? ? ? ? return Ok(GetShippingMethods());
? ? ? ? }        
No alt text provided for this image

Getting Started with Strategy Pattern

The first step to implement the Strategy pattern is to declare a common interface that will be implemented by all the algorithms. For our demo app, let’s call it?IShippingStrategy?interface. The interface has a single method?CalculateFinalTotal?that accepts?orderTotal?as a parameter and returns the final total.

IShippingStragegy.cs

? ? public interface IShippingStrategy
? ? {
? ? ? ? decimal CalculateFinalTotal(decimal orderTotal);
? ? }        

The next step is to implement the above interface on all the algorithms. For our demo app, we need three algorithms Free Shipping, Local Shipping, and Worldwide Shipping to calculate the final total with different shipping rates. Let’s implement all three algorithms one by one.

FreeShippingStrategy.cs

? ? public class FreeShippingStrategy : IShippingStrategy
? ? {
? ? ? ? public decimal CalculateFinalTotal(decimal orderTotal)
? ? ? ? {
? ? ? ? ? ? return orderTotal;
? ? ? ? }
? ? }        

LocalShippingStrategy.cs

public class LocalShippingStrategy : IShippingStrategy
? ? {
? ? ? ? public decimal CalculateFinalTotal(decimal orderTotal)
? ? ? ? {
? ? ? ? ? ? return orderTotal + 10;
? ? ? ? }
? ? }        

WorldwideShippingStrategy.cs

? ? public class WorldwideShippingStrategy : IShippingStrategy
? ? {
? ? ? ? public decimal CalculateFinalTotal(decimal orderTotal)
? ? ? ? {
? ? ? ? ? ? return orderTotal + 50;
? ? ? ? }
? ? }        

I implemented very simple shipping rates calculations in the above classes but in a real-world application, these classes can have all sorts of complex logics. They can also call third-party shipping APIs to calculate shipping rates for different destinations.

Let’s continue our Strategy pattern implementation and declare the following?IShippingContext?interface.

IShippingContext.cs

? ? public interface IShippingContext
? ? {
? ? ? ? void SetStrategy(IShippingStrategy strategy);


? ? ? ? decimal ExecuteStrategy(decimal orderTotal);
? ? }        

Next, implement the above interface on the following?ShippingContext?class.

ShippingContext.cs

? ? public class ShippingContext : IShippingContext
? ? {
? ? ? ? private IShippingStrategy _strategy;


? ? ? ? public ShippingContext()
? ? ? ? { }


? ? ? ? public ShippingContext(IShippingStrategy strategy)
? ? ? ? {
? ? ? ? ? ? this._strategy = strategy;
? ? ? ? }


? ? ? ? public void SetStrategy(IShippingStrategy strategy)
? ? ? ? {
? ? ? ? ? ? this._strategy = strategy;
? ? ? ? }


? ? ? ? public decimal ExecuteStrategy(decimal orderTotal)
? ? ? ? {
? ? ? ? ? ? return this._strategy.CalculateFinalTotal(orderTotal);
? ? ? ? }
? ? }        

We declared a field _strategy?for storing the reference to a strategy object. The?SetStrategy?method will allow us to replace the strategy with the strategy passed into the parameter of the method. Finally, we have the implementation of the?ExecuteStrategy?method that executes the?CalculateFinalTotal?for the current strategy.

Using Strategy Pattern in ASP.NET Core

We are now ready to use different shipping algorithms in our?HomeController?usingthe above?ShippingContext?class but first, we need to register it with .NET Core DI Container.

? ? public static class BusinessServiceExtension
? ? {
? ? ? ? public static void AddBusiness(this IServiceCollection services)
? ? ? ? {
? ? ? ? ? ? services.AddScoped<IShippingContext, ShippingContext>();
? ? ? ? }
? ? }        

We can achieve this by adding the following line in the?ConfigureService?method of?Startup.cs?file.

? ? ? ? public void ConfigureServices(IServiceCollection services)
? ? ? ? {
? ? ? ? ? ? services.AddBusiness();


? ? ? ? ? ? services.AddControllers();
? ? ? ? ? ? services.AddSwaggerGen(c =>
? ? ? ? ? ? {
? ? ? ? ? ? ? ? c.SwaggerDoc("v1", new OpenApiInfo { Title = "StrategyDesignPattern", Version = "v1" });
? ? ? ? ? ? });
? ? ? ? }        

Next, we need to inject the?IShippingContext?in our?HomeController

[ApiController]
[Route("[controller]")]
public class HomeController : ControllerBase
{
  private readonly ILogger<HomeController> _logger;
  private readonly IShippingContext _shippingContext;
  
  
  public HomeController(ILogger<HomeController> logger, IShippingContext shippingContext)
  {
    _logger = logger;
    _shippingContext = shippingContext;
  }
}        

Finally, we can implement our?SubmitOrderDetails action method that will be called when the user will post SubmitCheckoutModel. Notice how we are using the?SetStrategy?method to associate the appropriate strategy according to the shipping method selected by the user. Once the strategy is set, the?ExecuteStrategy?will automatically use the appropriate shipping strategy to calculate the final total.

[HttpPost]
[Route("SubmitOrderDetails")]
public IActionResult SubmitOrderDetails(SubmitCheckoutModel model)
{
  
  switch (model.SelectedMethod)
  {
    case SelectedMethodEnum.FreeShipping:
      _shippingContext.SetStrategy(new FreeShippingStrategy());
      break;
    case SelectedMethodEnum.LocalShipping:
      _shippingContext.SetStrategy(new LocalShippingStrategy());
      break;
    case SelectedMethodEnum.WorldwideShipping:
      _shippingContext.SetStrategy(new WorldwideShippingStrategy());
      break;
  }
      
  return Ok(new CheckoutModel
  {
    FinalTotal = _shippingContext.ExecuteStrategy(model.OrderTotal),
    OrderTotal = model.OrderTotal,
    SelectedMethod = (int)model.SelectedMethod
  });
 }        

Run the demo app and try to calculate the final total by selecting different shipping methods. You will the final order total accordingly.

No alt text provided for this image

The following is the complete source code of our?HomeController.

HomeController.cs

[ApiController]
? ? [Route("[controller]")]
? ? public class HomeController : ControllerBase
? ? {
? ? ? ? private readonly ILogger<HomeController> _logger;
? ? ? ? private readonly IShippingContext _shippingContext;


? ? ? ? public HomeController(ILogger<HomeController> logger, IShippingContext shippingContext)
? ? ? ? {
? ? ? ? ? ? _logger = logger;
? ? ? ? ? ? _shippingContext = shippingContext;
? ? ? ? }


? ? ? ? [HttpGet]
? ? ? ? [Route("GetShippingMethods")]
? ? ? ? public IActionResult Get()
? ? ? ? {
? ? ? ? ? ? return Ok(GetShippingMethods());
? ? ? ? }


? ? ? ? [HttpPost]
? ? ? ? [Route("SubmitOrderDetails")]
? ? ? ? public IActionResult SubmitOrderDetails(SubmitCheckoutModel model)
? ? ? ? {


? ? ? ? ? ? switch (model.SelectedMethod)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? case SelectedMethodEnum.FreeShipping:
? ? ? ? ? ? ? ? ? ? _shippingContext.SetStrategy(new FreeShippingStrategy());
? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? case SelectedMethodEnum.LocalShipping:
? ? ? ? ? ? ? ? ? ? _shippingContext.SetStrategy(new LocalShippingStrategy());
? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? case SelectedMethodEnum.WorldwideShipping:
? ? ? ? ? ? ? ? ? ? _shippingContext.SetStrategy(new WorldwideShippingStrategy());
? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? }


? ? ? ? ? ? return Ok(new CheckoutModel
? ? ? ? ? ? {
? ? ? ? ? ? ? ? FinalTotal = _shippingContext.ExecuteStrategy(model.OrderTotal),
? ? ? ? ? ? ? ? OrderTotal = model.OrderTotal,
? ? ? ? ? ? ? ? SelectedMethod = (int)model.SelectedMethod
? ? ? ? ? ? });
? ? ? ? }


? ? ? ? private List<ShippingMethod> GetShippingMethods()
? ? ? ? {
? ? ? ? ? ? return new List<ShippingMethod>()
? ? ? ? ? ? {
? ? ? ? ? ? ? ? new ShippingMethod()
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? Id = 1,
? ? ? ? ? ? ? ? ? ? Name="Free Shipping ($0.00)"
? ? ? ? ? ? ? ? },
? ? ? ? ? ? ? ? new ShippingMethod() {
? ? ? ? ? ? ? ? ? ? Id = 2,
? ? ? ? ? ? ? ? ? ? Name="Local Shipping ($10.00)"
? ? ? ? ? ? ? ? },
? ? ? ? ? ? ? ? new ShippingMethod() {
? ? ? ? ? ? ? ? ? ? Id = 3,
? ? ? ? ? ? ? ? ? ? Name="Worldwide Shipping ($50.00)"
? ? ? ? ? ? ? ? }
? ? ? ? ? ? };
? ? ? ? }
? ? }        

Summary

Strategy pattern helps us in creating efficient, more readable, and easy to maintain software. It allows us to create software with reusable and interchangeable components which can be added/replaced dynamically at runtime. I hope you enjoyed this tutorial and the example I use to explain this pattern in a way that is easy to understand for everyone.

See source code.

要查看或添加评论,请登录

Matija Katadzic的更多文章

  • Send SMS messages with C# & .net6

    Send SMS messages with C# & .net6

    In modern web applications, the business may require to send SMS to end-users in various scopes like Alerting the…

  • Run and manage periodic background tasks in ASP.NET Core 6 with C#

    Run and manage periodic background tasks in ASP.NET Core 6 with C#

    Sometimes your web app needs to do work in the background periodically e.g.

  • Mapping Experiment in .net core- AutoMapper, ExpressMapper, Mapster & Manual mapping

    Mapping Experiment in .net core- AutoMapper, ExpressMapper, Mapster & Manual mapping

    Nuget package that we need: You can read more in official pages: AutoMapper - https://automapper.org/, BenchmarkDotNet…

  • What is a Decorator Pattern?

    What is a Decorator Pattern?

    The decorator pattern (also known as Wrapper) is a structural design pattern and it allows developers to dynamically…

  • What is Adapter Design Pattern?

    What is Adapter Design Pattern?

    The Adapter design pattern is one of the most common and useful structural design patterns. It allows objects with…

  • What is a Builder Pattern?

    What is a Builder Pattern?

    Builder Design Pattern allows us to separate the construction of a complex object from its representation so that we…

  • What is Observer Design Pattern?

    What is Observer Design Pattern?

    The Observer is a behavioral design pattern and it allows some objects (known as subjects or publishers or observables)…

  • What’s a design pattern?

    What’s a design pattern?

    Design patterns are typical solutions to commonly occurring problems in software design. They are like pre-made…

  • What is gRPC?

    What is gRPC?

    What is gRPC? gRPC (gRPC Remote Procedure Calls) is an open source remote procedure call (RPC) system initially…

社区洞察

其他会员也浏览了