NashTech Blog

Introduction to Strategy Design Pattern

Table of Contents
code, coding, computer-1839406.jpg

What is Strategy Design Pattern?

The strategy pattern, a behavioural design pattern within the renowned Gang of Four (GoF) collection frequently utilised in object-oriented programming, permits the dynamic selection of an object’s behaviour during runtime.

In simpler terms, it empowers you to establish a collection of algorithms, allocate each to its own class, and facilitate interchangeability of their objects.

Characteristics of the Strategy Design Pattern

The Strategy Design Pattern possesses several defining attributes that render it both distinctive and highly effective for handling algorithmic variations within software systems:

  • It specifies a group of algorithms: This pattern enables the encapsulation of numerous algorithms or behaviours into distinct classes, referred to as strategies.
  • It encapsulates behavioural logic within its structure: Every strategy contains a distinct behaviour or algorithm, offering a tidy and modular approach to oversee diverse variations or implementations.
  • It facilitates dynamic switching of behaviours: The pattern grants clients the ability to transition between various strategies on-the-fly, enabling versatile and fluid adjustments in behaviour.
  • It encourages object cooperation: The pattern fosters collaboration between a context object and its strategy counterparts, with the context assigning behaviour execution to a strategy object.

Components of Strategy Pattern

Context:

The Context, either as a class or an object, retains a reference to a strategy object and delegates tasks to it. It serves as the intermediary between the client and the strategy, offering a uniform approach to execute tasks without needing to understand the intricacies of their implementation. The Context retains a reference to a strategy object and invokes to methods to accomplish tasks, facilitating the utilization of interchangeable strategies.

Strategy Interface

The strategy interface, whether an interface or an abstract class, outlines a series of methods that all concrete strategies must fulfil. It acts as a covenant, guaranteeing that all strategies confirm to a uniform set of guidelines and can be seamlessly interchanged by the Context. By establishing a shared interface, the Strategy interface facilitates decoupling between the context and the concrete strategies, fostering flexibility and modularity in the design.

Concrete Strategies

Concrete strategies represent distinct implementations of the Strategy interface. Each concrete strategy furnishes a unique algorithm or behaviour tailored to execute the task stipulated by the Strategy interface.

These strategies encapsulate the intricacies of their individual algorithms and furnish a method for task execution. They are interchangeable entities, allowing the client to select and configure them based on the task requirements.

Client

The Client has the role of selecting and configuring the suitable strategy, furnishing it to the Context. With an understanding of the task requirements, the client determines the strategy to employ. It instantiates the desired concrete strategy and transfers it to the Context, thereby enabling the Context to utilize the chosen strategy for the task execution.

Interaction between the Components

In this pattern, the components interact in a structured and decoupled manner. Here’s how the communication between the components takes place:

  • Client to Context:

    • The Client initiates the task execution by selecting an appropriate strategy based on the requirements and provide that to the Context.
    • Before passing the strategy to the Context, the client may make changes to the selected strategy if that is required.
  • Context to Strategy:

    • The Context keeps the reference to strategy provided by the client and delegates task to it.
    • The Context is responsible for triggering the execution of the algorithm or behaviour encapsulated within the strategy by invoking a method on the strategy object.
  • Strategy to Context:

    • After the completion of the task execution, the strategy returns the result or any relevant information back to the Context, which may further utilise the result or perform other tasks as needed.
  • Strategy Interface and Context:

    • The strategy interface serves as a contract that defines all the methods that is required and are implemented by the concrete strategies.
    • The Context uses these strategy interfaces to communicate with the strategies making the interchangeability and decoupling possible.

Whole communication between the components is decoupled, which means that the Context is not aware of how an algorithm is implemented or how the strategy given to it by the client is implementing the tasks. These strategies can be swapped or replaced without making any changes to the client or other strategies.

To summarize this, the communication primarily entails the Client providing a strategy to the Context and Context class invoking a method on the given strategy object. This action initiates the execution of a distinct algorithm or behaviour, facilitating task completion. By segregating task execution from strategy selection and configuration, the pattern fosters flexibility, modularity and code reusability across the software system.

Example

Let’s take an example where we need to calculate the total cost of all the developers’ salaries in a single organisation. The main task here is to calculate the total cost for the developer’s salaries, but for the different developer levels, the salary is calculated differently.

Introducing the Strategy design pattern to the task of calculating the total cost for developer salaries offers a structured approach to handle the varying salary calculations based on different developer levels.

By employing this pattern, we can encapsulate each salary calculating algorithm into separate strategies, allowing for dynamic selection and interchangeable use at runtime.

This modification promotes flexibility and maintainability, ensuring that the system remains adaptable to future changes calculation rules or the addition of new developer levels.

Implementation

Let’s start with the DeveloperLevel enumeration and a simple DeveloperReport object

public enum DeveloperLevel

{ 
  Senior, 
  Junior
}

public class DeveloperReport
{
  public int Id {get; set;}
  public string Name {get; set;}
  public DeveloperLevel Level {get; set;}
  public int WorkingHours {get; set;}
  public double HourlyRate {get; set;}

  public double CalculateSalary() => 
    WorkingHours * HourlyRate;
}

Now, let’s create the Strategy interface named ISalaryCalculator:

public interface ISalaryCalculator 
{ 
  double CalculateTotalSalary(IEnumerable<DeveloperReport> reports); 
}

Now what we need is the concrete strategy objects which will accept all the reports and calculate the total salary for the required developer levels:

public class JuniorDevSalaryCalculator:ISalaryCalculator 
{ 
  public double CalculateTotalSalary(IEnumerable<DeveloperReport> reports) => 
    reports 
    .Where(r => r.Level == Developer.Junior) 
    .Select(r => r.CalculateSalary()) .Sum(); 
} 

public class SeniorDevSalaryCalculator:ISalaryCalculator 
{ 
  public double CalculateTotalSalary(IEnumerable<DeveloperReport> reports) => 
    reports 
    .Where(r => r.Level == Developer.Senior) 
    .Select(r => r.CalculateSalary()*1.2) 
    .Sum(); 
}

Once we have the strategy objects, we need the context object:

public class SalaryCalculator 
{ 
  private ISalaryCalculator _calculator; 
  public SalaryCalculator(ISalaryCalculator calculator) 
  { 
    _calculator = calculator; 
  } 
  public void SetCalculator(ISalaryCalculator calculator) =>
    _calculator = calculator; 
  public double Calculate(IEnumerable<DeveloperReport> reports) => 
    _calculator.CalculateTotalSalary(reports); 
}

Now, Program.cs

class Program
{
  static void Main(string[] args)
  {
    var reports = new List<DeveloperReport>
    {
      new DeveloperReport { Id = 1, Name = "David", Level = Developer.Senior, HourlyRate = 30.5, WorkingHours = 160 },
      new DeveloperReport { Id = 2, Name = "David Jr.", Level = Developer.Junior, HourlyRate = 20, WorkingHours = 120 }
    };
    var calculatorContext = new SalaryCalculator(new JuniorDevSalaryCalculator());
    var juniorTotal = calculatorContext.Calculate(reports);
    Console.WriteLine($"Total amount for Jr. salaries is: {juniorTotal});
    
    calculatorContext.SetCalculator(new SeniorDevSalaryCalculator());
    var seniorTotal = calculatorContext.Calculate(reports);
    Console.WriteLine($"Total amount for Jr. salaries is: {seniorTotal});
    Console.WriteLine($"Total cost for all the salaries is: {juniorTotal + seniorTotal });
  }
}

When to use Strategy Design Pattern?

The strategy pattern is ideal when we encounter varying functionalities within an object and require seamless switching between these variations at runtime. Additionally, if our project involves similar classes distinguished solely by their methods of behaviour execution, then the Strategy pattern becomes the optimal choice.

Conclusion

Implementing the Strategy Design Pattern is straightforward and not complex. It enhances code readability and simplifies maintenance. While it necessitates the creation of additional classes in the project, the benefits outweigh this requirement.

 

Picture of vikashkumar

vikashkumar

Leave a Comment

Your email address will not be published. Required fields are marked *

Suggested Article

Scroll to Top