Skip to main content

Strategy Design Pattern :

Strategy is a plan to achieve some goal. Let's see what Strategy design pattern says :

Strategy is a Behavioral type of design pattern.

Behavioral Design Pattern : It's about object communication, their responsibilities and how they communicate each other.

The strategy pattern group of similar algorithms to be defined. The algorithm to be used for a particular requirement may then be called at run-time.

I will try to explain this pattern through example :

Here we have an Employee class which has 3 properties Name, Address and EmployeeDepartment . We want to get the bonus of employee and the calculation is done based on the department the Employee belongs to. In real time there will be some complex logic to calculate the bonus, for example purpose I am just returning some values.

We have a class EmployeeBonusCalculatorService which has a Method Calculate bonus. Calculate Bonus takes the Employee object as parameter and based on Employee type(switch case), it calls the respective Bonus calculation methods.

  public class Address
    {
        public string ContactName { get; set; }
        public string AddressLine1 { get; set; }
        public string AddressLine2 { get; set; }
        public string AddressLine3 { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public string Country { get; set; }
        public string PostalCode { get; set; }
    }


public enum EmployeeDepartment

    {
        Sales,
        IT,
        Finance
    }

 public class Employee
    {
        public string Name { get; set; }
        public Address Address { get; set; }
        public EmployeeDepartment EmployeeDepartment { get; set; }
    }


 public class EmployeeBonusCalculatorService
    {
     
        public int CalculateBonus(Employee employee)
        {

            switch (employee.EmployeeDepartment)
            {
                case EmployeeDepartment.Finance:
                    return CalculateBonusForFinance(employee);
                case EmployeeDepartment.IT:
                    return CalculateBonusForIT(employee);
                case EmployeeDepartment.Sales:
                    return CalculateBonusForSales(employee);
                default:
                    throw new Exception("Unknown Error");
                  
            }
        }

        private int CalculateBonusForSales(Employee employee)
        {
            return 300;
        }

        private int CalculateBonusForIT(Employee employee)
        {
            return 500;
        }

        private int CalculateBonusForFinance(Employee employee)
        {
            return 400;
        }
    }

Run the console application with 

class Program

    {
        static void Main(string[] args)
        {
            EmployeeBonusCalculatorService _service = 
                new StrategyPattern.EmployeeBonusCalculatorService();
           int bonus= _service.CalculateBonus(new StrategyPattern.Employee {
                Name = "Max",
                Address = new Address {
                    ContactName = "Marry",
                    AddressLine1 = "302 Nagrajan Building",
                    AddressLine2 = "Kadugudi",
                    AddressLine3 = "Near Devi Temple",
                    City = "Bangalore",
                    State= "Karnataka",
                    PostalCode = "560076",
                    Country = "India"
                },
                EmployeeDepartment = EmployeeDepartment.Finance
            });
            Console.WriteLine(bonus);
        }
    }

Output would be : 400










Run the program with different EmployeeDepartment values i.e. EmployeeDepartment.Sales / EmployeeDepartment.IT. You will get the correct response.

Did you find any problem in above code ?

Actually, this code will always give correct response but this code has a major design flaw.

Let's assume one more department is added, to accommodate new department what we'll have to do ?

We'll have to modify our class  EmployeeBonusCalculatorService and add 1 more switch case along with respective BonusCalculation Method which s breaking an Open/Closed Principle of SOLID principle which states that Entities should be open for extension but closed for modification. Also EmployeeBonusCalculatorService accomodating the logic of different Employee Departments which is breaking Single Responsibility Principle. Another principle that is breaking with the above design is "Change is one class is causing change in another class" If you remove or add any department type you need to come back to your service class and change the code accordingly. If you require to change one part of your system because of change in another part of the sytem, it means system is tightly coupled.

Solution

Create separate Strategy class for each Department Type and each strategy should implement a common interface IDepartmentStrategy.

 public interface IDepartmentStrategy
    {
        int CalculateBonus(Employee emp);
    }

I have modified Employee class a bit and removed property EmployeeDepartment from it, may be or may not be present (depends on domain experts, I have just removed as after design change it won't be required anymore)

  public class Employee
    {
        public string Name { get; set; }
        public Address Address { get; set; }
    }

//Strategy Implentation
public class FinanceStrategy : IDepartmentStrategy
    {
        public int CalculateBonus(Employee emp)
        {
            return 400;
        }
    }

    public class SalesStrategy : IDepartmentStrategy
    {
        public int CalculateBonus(Employee emp)
        {
            return 300;
        }
    }

    public class ITStrategy : IDepartmentStrategy
    {
        public int CalculateBonus(Employee emp)
        {
            return 500;
        }
    }

//Modify Service Class

public class EmployeeBonusCalculator_With_Strategy_Service
    {
        IDepartmentStrategy _departmentStrategy;
        public EmployeeBonusCalculator_With_Strategy_Service(IDepartmentStrategy departmentStrategy)
        {
            _departmentStrategy = departmentStrategy;
        }
        public int CalculateBonus(Employee employee)
        {
            return _departmentStrategy.CalculateBonus(employee);
        }

   Call from Main method

 class Program
    {
        static void Main(string[] args)
        {
            IDepartmentStrategy strategy = new FinanceStrategy();
            EmployeeBonusCalculator_With_Strategy_Service _service = 
                new StrategyPattern.EmployeeBonusCalculator_With_Strategy_Service(strategy);
           int bonus= _service.CalculateBonus(new StrategyPattern.Employee {
                Name = "Max",
                Address = new Address {
                    ContactName = "Marry",
                    AddressLine1 = "302 Nagrajan Building",
                    AddressLine2 = "Kadugudi",
                    AddressLine3 = "Near Devi Temple",
                    City = "Bangalore",
                    Region = "Karnataka",
                    PostalCode = "560076",
                    Country = "India"
                }
            });
            Console.WriteLine(bonus);
        }
    }

Output:  Same 400









Now Add another Department type

 public enum EmployeeDepartment
    {
        Sales,
        IT,
        Finance,
        HR
    }
 Add corresponding Strategy 

 public class HRStrategy : IDepartmentStrategy
    {
        public int CalculateBonus(Employee emp)
        {
            return 600;
        }
    } 

Now, no need to modify Service Class Just call Program -> Main method

 class Program
    {
        static void Main(string[] args)
        {
            IDepartmentStrategy strategy = new HRStrategy();
            EmployeeBonusCalculator_With_Strategy_Service _service = 
                new StrategyPattern.EmployeeBonusCalculator_With_Strategy_Service(strategy);
           int bonus= _service.CalculateBonus(new StrategyPattern.Employee {
                Name = "Max",
                Address = new Address {
                    ContactName = "Marry",
                    AddressLine1 = "302 Nagrajan Building",
                    AddressLine2 = "Kadugudi",
                    AddressLine3 = "Near Devi Temple",
                    City = "Bangalore",
                    Region = "Karnataka",
                    PostalCode = "560076",
                    Country = "India"
                }
            });
            Console.WriteLine(bonus);
        }
    }

Output:600







Another way to implement Strategy is :

Define strategy

  public class DepartmentStrategy
    {
        public Func<Employee, int> ITStrategy = delegate (Employee employee) { return 500; };
        public Func<Employee, int> SalesStrategy = delegate (Employee employee) { return 300; };
        public Func<Employee, int> FinanceStrategy = delegate (Employee employee) { return 400; };
    }

Modify service class as :

 public class EmployeeBonusCalculator_With_Strategy_Service
    {
        public int CalculateBonus(Employee employee, Func<Employee,int> departmentStrategy)
        {
            return departmentStrategy(employee);
        }
    }

Main Method of Program Class:

class Program
    {
        static void Main(string[] args)
        {
            EmployeeBonusCalculator_With_Strategy_Service _service =
                new StrategyPattern.EmployeeBonusCalculator_With_Strategy_Service();
            Employee emp = new StrategyPattern.Employee
            {
                Name = "Max",
                Address = new Address
                {
                    ContactName = "Marry",
                    AddressLine1 = "302 Nagrajan Building",
                    AddressLine2 = "Kadugudi",
                    AddressLine3 = "Near Devi Temple",
                    City = "Bangalore",
                    Region = "Karnataka",
                    PostalCode = "560076",
                    Country = "India"
                }
            };

            int bonus = _service.CalculateBonus(emp, new DepartmentStrategy().FinanceStrategy);
            Console.WriteLine(bonus);
        }
    }
Output:400










You can get rid of unpleasant if -else or switch code and would be able to write a clean code along with decoupled design and other design issues discussed above.

You can find it here also C-Sharp-Corner

Comments

Popular posts from this blog

Abstract Factory Design Pattern

Abstract Factory : return families of related or dependent objects without specifying their concrete classes. AbstractFactory- IVehicle ConcreteFactory - Maruti, Honda AbstractProduct- IDiesel, IPetrol Product- DezirePetrol, ZenDiesel, AmazeDiesel, CityPetrol Client- This is a class which uses AbstractFactory and AbstractProduct interfaces to create a family of related objects Implementation is pretty much straight forward: Since now you have good understanding of Factory, there is nothing much to explain. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AbstractFactory {     class Program     {         static void Main(string[] args)         {             IVehicle marutiVehicle = new Maruti();             CarClient marutiClient = new CarClient(marutiVehicle); ...

Database Factory Design Pattern C#

Many developers has confusion on how to write database code in application. I will discuss several approaches here and try to provide explanation on each approach. Approach 1 : Easiest approach which is very straight forward which will work absolutely fine. The issues we will get to manage the project in terms of software principles. Like if you going to change Database provider, you would need to change using statements and other issue like mixing Database code with business logic etc. I will not go in detail here, I am assuming you already have an understanding of design principles. I am just exploring different approaches to achieve the database connectivity. using System.Collections.Generic; using System.Data.SqlClient; namespace DatabaseFactory {     class Program     {         static void Main(string[] args)         {             var employees = SomeDabOperation();   ...

Dependency Injection and Inversion Of Control C# - Par 2 (Implementation)

Before going through this post, I suggest you to go through my previous post on basic concepts in Dependency injection and Inversion of control. Demo on product. This demo is based on a traditional implementation approach: We have a Product class and it has a method which takes the discount and based on some business logic it applies the discount on the product. Product service will interact with database using ProductRepository. If you will notice ProductService has dependency on 2 concrete classes i.e. Discount and ProductRepository new Discount(); new ProductRepository(); We can hence say that ProductService is tightly coupled because of these 2 dependencies. We may require to use different discount based on several factors such as Product type, Some Festival or any other factor. Based on different discounts we may need to change ProductService class also in future you may change the database from SQL to Oracle, Again it may require ProductService changes which is viol...