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 with each other.
The strategy pattern is a 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 an 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 purposes 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);
- }
- }
Run the program with different EmployeeBBrect response.
Did you find any problem in the 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 will we have to do ?
We'll have to modify our class EmployeeBonusCalculatorService and add 1 more switch case along with respective BonusCalculation Method which is 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 need to change one part of your system because of a change in another part of the sytem, it means system is tightly coupled.
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 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;
- }
- }
-
- 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;
- }
- }
-
- 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);
- }
- }
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);
- }
- }
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