Understanding the Strategy Pattern: A Flexible Approach to Salary Processing (Python)

Daniel Azevedo - Sep 11 - - Dev Community

Hey everyone!

Today, I want to talk about a design pattern that’s incredibly useful when you need flexibility in how you process tasks: the Strategy Pattern. This pattern is all about choosing different algorithms or "strategies" at runtime without changing the code that uses them. To make this more concrete, let’s take a look at how it can be applied in an HR system for salary processing.

Why the Strategy Pattern Matters

The Strategy Pattern comes in handy when you need to switch between different ways of doing the same thing—without hard-coding logic into your classes. It’s particularly useful when you have different rules or methods to handle a specific task, like calculating salaries based on employee types (e.g., full-time, part-time, or contractors).

Here are some benefits of using the Strategy Pattern:

  • Clean Code: It keeps your code clean by avoiding endless if-else or switch statements.
  • Flexibility: You can add or modify strategies without altering the existing logic.
  • Maintainability: Your code becomes easier to maintain and extend, since each strategy is separated into its own class or function.

Example: Salary Processing in Python

Let’s take a scenario where you need to calculate the salary for different types of employees. Full-time, part-time, and contractor employees all have different salary structures and tax calculations.

Without the Strategy Pattern, your code might look like this:

class SalaryProcessor:
    def calculate_salary(self, employee_type):
        if employee_type == 'full_time':
            return 5000 - (5000 * 0.2)  # 20% tax
        elif employee_type == 'part_time':
            return 2000 - (2000 * 0.1)  # 10% tax
        elif employee_type == 'contractor':
            return 3000 - (3000 * 0.15)  # 15% tax
        else:
            raise ValueError("Invalid employee type")
Enter fullscreen mode Exit fullscreen mode

This works, but it's not scalable. What if you need to add a new employee type or change how taxes are calculated? You’d have to keep modifying this class, which could quickly become messy.

Applying the Strategy Pattern

Now let’s implement the Strategy Pattern to make this process more flexible and maintainable:

Step 1: Define the Salary Strategy Interface

In Python, we can simply use abstract methods or a base class to define our strategy interface.

from abc import ABC, abstractmethod

class SalaryStrategy(ABC):
    @abstractmethod
    def calculate_salary(self, base_salary):
        pass
Enter fullscreen mode Exit fullscreen mode

Step 2: Implement Concrete Strategies

Next, we create specific strategies for each employee type. Each strategy will implement the logic for calculating the salary, including tax.

class FullTimeSalaryStrategy(SalaryStrategy):
    def calculate_salary(self, base_salary):
        return base_salary - (base_salary * 0.2)  # 20% tax

class PartTimeSalaryStrategy(SalaryStrategy):
    def calculate_salary(self, base_salary):
        return base_salary - (base_salary * 0.1)  # 10% tax

class ContractorSalaryStrategy(SalaryStrategy):
    def calculate_salary(self, base_salary):
        return base_salary - (base_salary * 0.15)  # 15% tax
Enter fullscreen mode Exit fullscreen mode

Step 3: Implement the Context

Now, we create a context that will use these strategies. The context is the class that handles the employee's salary but delegates the calculation to the appropriate strategy.

class SalaryContext:
    def __init__(self, strategy: SalaryStrategy):
        self._strategy = strategy

    def set_strategy(self, strategy: SalaryStrategy):
        self._strategy = strategy

    def calculate_salary(self, base_salary):
        return self._strategy.calculate_salary(base_salary)
Enter fullscreen mode Exit fullscreen mode

Step 4: Using the Strategy

Now, we can easily switch between salary strategies based on the employee type at runtime.

# Full-time employee
full_time_salary = SalaryContext(FullTimeSalaryStrategy())
print(f"Full-time salary: {full_time_salary.calculate_salary(5000)}")

# Part-time employee
part_time_salary = SalaryContext(PartTimeSalaryStrategy())
print(f"Part-time salary: {part_time_salary.calculate_salary(2000)}")

# Contractor
contractor_salary = SalaryContext(ContractorSalaryStrategy())
print(f"Contractor salary: {contractor_salary.calculate_salary(3000)}")
Enter fullscreen mode Exit fullscreen mode

Why This Approach is Better

  1. Separation of Concerns: Each salary calculation is handled by its own class, so you don’t have a bloated if-else or switch statement.
  2. Scalability: Need to add a new employee type? Just implement a new strategy and plug it in—no need to modify existing code.
  3. Easy to Modify: If the tax rules change, you only need to modify the specific strategy without touching the rest of the code.

When Should You Use the Strategy Pattern?

The Strategy Pattern is a great fit when:

  • You have multiple ways to perform the same operation (e.g., calculating salary based on different employee types).
  • You want to keep your code clean, flexible, and maintainable as new requirements or rules are introduced.
  • You need to switch between different algorithms dynamically, at runtime.

Wrapping Up

The Strategy Pattern is a powerful tool for situations where flexibility is needed in applying different algorithms or processes. In this salary processing example, we’ve seen how separating the salary calculation logic into different strategies makes the system much easier to extend and maintain.

Have you used the Strategy Pattern in your projects? If so, I’d love to hear how it worked for you!

Happy coding!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player