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
Cons of Strategy Pattern
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
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());
? ? ? ? }
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.
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.