Design Pattern
.NET .NET Core C# Patterns Visual Studio

Using Design Patterns in .NET Core – Part 3 – Strategy

Welcome to today’s post.

In today’s post I will be discussing the Strategy design pattern and how it can be used within a .NET Core application.

In a previous post I discussed the factory pattern, which is a creational pattern that abstracts the generation of instances of derived classes.

Before I go ahead and show how to implement a strategy pattern using classes, I will explain what the Strategy Design Pattern is in the next section.

Explaining the Strategy Pattern

The strategy pattern is categorized as a behavioural design pattern. A behavioural design pattern deals with algorithms and responsibilities. A strategy pattern allows an object to switch algorithms depending on various contextual conditions.

In our example of a library Loan, we can use the date and time context to determine the return type when a loan is returned to the library.

Depending on the date and time, we can determine the method of return as being an unattended return from an external Return Chute or an attended return from the library Returns Counter.

Below is a diagram depicting a strategy interface, IBookReturnStrategy, which has different implementations depending on the time of day and days within the year:

I will show how we implement the above strategy in the next section.

Implementation of a Strategy Pattern from Interfaces

In this section I will show how to implement different strategies based on a strategy pattern from an interface.

We define our return strategy interface as follows:

namespace BookLoan.Loan.API.Strategy
{
    public interface IBookReturnStrategy
    {
        public string DayOfReturn { get; set; } 
        public bool IsAfterHoursFlag { get; set; }
        public bool IsPublicHolidayFlag { get; set; }
        public string ExecuteReturn();
    }
}

Our concrete strategies are then extended from the interface strategy.

The after-hours return strategy is shown:

namespace BookLoan.Loan.API.Strategy
{
    public class ConcreteBookReturnAfterHours: IBookReturnStrategy
    {
        public ConcreteBookReturnAfterHours()
        {
            DayOfReturn = "";
            IsAfterHoursFlag = true;
            IsPublicHolidayFlag = false;
        }

        public string DayOfReturn { get; set; }
        public bool IsAfterHoursFlag { get; set; }
        public bool IsPublicHolidayFlag { get; set; }

        public string ExecuteReturn()
        {
            return _isAfterHours ? "After Hours Chute": "Returns Desk";
        }
    }
}

The normal-hours return strategy is shown:

namespace BookLoan.Loan.API.Strategy
{
    public class ConcreteBookReturnNormalHours: IBookReturnStrategy
    {
        public ConcreteBookReturnNormalHours()
        {
            DayOfReturn = "";
            IsAfterHoursFlag = false;
            IsPublicHolidayFlag = false;
        }

        public string DayOfReturn { get; set; }
        public bool IsAfterHoursFlag { get; set; }
        public bool IsPublicHolidayFlag { get; set; }

        public string ExecuteReturn()
        {
            return !isAfterHours ? "Returns Desk": "After Hours Chute";
        }
    }
}

The public-holidays return strategy is shown:

namespace BookLoan.Loan.API.Strategy
{
    public class ConcreteBookReturnPublicHolidays: IBookReturnStrategy
    {
        public ConcreteBookReturnPublicHolidays()
        {
            DayOfReturn = "";
            IsAfterHoursFlag = false;
            IsPublicHolidayFlag = true;
        }

        public string DayOfReturn { get; set; }
        public bool IsAfterHoursFlag { get; set; }
        public bool IsPublicHolidayFlag { get; set; }

        public string ExecuteReturn()
        {
            return _isPublicHoliday ? "After Hours Chute": "Returns Desk";
        }
    }
}

In the next section, I will show how we can implement a class that allows us to switch between strategy implementations based on the current date and time.

Implementation of a Strategy Switching Class

In this section, I will show how to implement a strategy switching class, BookReturn, that returns a different return method that depends on a concrete instance of a strategy that changes depending on the current date and time.

We first define a context class to contain our strategy switching instance. This is shown below:

using System;

namespace BookLoan.Loan.API.Strategy
{
    public interface IBookReturn
    {
        public string GetReturnMethod();
        public bool IsPublicHoliday(DateTime date);
        public bool IsTimeAfterHours(DateTime date);
        public bool IsTimeOpeningHours(DateTime date);
    }
}

The Book Return class contains a method ResolveBookReturnStrategry(), that changes strategy depending on the date and time, and a method GetReturnMethod(), that calls the common strategy method ExecuteReturn() that returns the execution method for that strategy.

The implementation is shown below:

using System;

namespace BookLoan.Loan.API.Strategy
{
    public class BookReturn: IBookReturn
    {
        public string GetReturnMethod()
        {
            IBookReturnStrategy bookReturnStrategy = this.ResolveBookReturnStrategry();
            return bookReturnStrategy.ExecuteReturn(); 
        }

        private IBookReturnStrategy ResolveBookReturnStrategry()
        {
            DateTime currentDate = DateTime.Now;

            if (this.IsPublicHoliday(currentDate))
                return new ConcreteBookReturnPublicHolidays();
            if (this.IsTimeAfterHours(currentDate))
                return new ConcreteBookReturnAfterHours();
            if (this.IsTimeOpeningHours(currentDate))
                return new ConcreteBookReturnNormalHours();
            
            throw new Exception("No existing strategy for book return");
        }

        public bool IsPublicHoliday(DateTime date)
        {
            if ((date.Month == 12) && (date.Day == 25))
                return true;
            if ((date.Month == 12) && (date.Day == 26))
                return true;
            if ((date.Month == 1) && (date.Day == 1))
                return true;
            if ((date.Month == 1) && (date.Day == 26))
                return true;
            return false;
        }

        public bool IsTimeAfterHours(DateTime date)
        {
            if ((date.Hour < 9) || (date.Hour > 18))
                return true;
            return false;
        }

        public bool IsTimeOpeningHours(DateTime date)
        {
            if ((date.Hour >= 9) && (date.Hour <= 18))
                return true;
            return false;
        }
    }
}

In the next section, I will show how to initialize the above class BookReturn in a .NET Core application startup.

Application of the Strategy Pattern in an Application

In this section I will show how to setup the implemented strategy pattern class in the startup, then I will show how to use it in a real situation where we determine the behaviour output, which is then updated within a loan record.

To configure our strategy patterns in the ConfigureServices() start-up we add our context class to the service collection:

services.AddTransient<IBookReturn, BookReturn>();

Now we can use the strategy pattern within a service class using dependency injection:

namespace BookLoan.Services
{
    public class LoanService: ILoanService
    {
        …
        private readonly IBookReturn _bookReturn;
        ApplicationDbContext _db;

        public LoanService(ApplicationDbContext db, 
                IBookReturn bookReturn,
        …
    )
    {
        _db = db;
        _bookReturn = bookReturn;
    }
    …

In our return loan return method, we utilise the strategy pattern instance to determine the return method:

public async Task ReturnLoan(LoanViewModel vm)
{
    vm.DateReturn = DateTime.Now;
    vm.ReturnMethod = _bookReturn.GetReturnMethod();
    _db.Update(vm);
    await _db.SaveChangesAsync();
}

As we can see, there was not much else we needed to do once the context class was instantiated. The behavior of the strategy was determined by a context which was the current date and time. In other applications our context could be driven by a cultural context or even data driven. Our context then determines which algorithm is chosen to determine a resulting behavior.

In addition, the strategy pattern satisfies the SOLID principles. We can inject the context into a class using dependency injection.  The separation of concerns is satisfied with each concrete strategy class. The open-closed principle is satisfied with the context extendable with additional strategy concrete classes. Interface segregation is supported with the context completely decoupled from the concrete strategy classes: it only uses what it needs in a particular context.

That’s all for today’s post.

I hope you found this post useful and informative.

Social media & sharing icons powered by UltimatelySocial