Using Strategy Design Pattern to Simplify Conditional Logic

Published on | Reading time: 6 min | Author: Andrés Reyes Galgani

Using Strategy Design Pattern to Simplify Conditional Logic
Photo courtesy of Patrick Lindenberg

Table of Contents

  1. Introduction
  2. Problem Explanation
  3. Solution with Code Snippet
  4. Practical Application
  5. Potential Drawbacks and Considerations
  6. Conclusion
  7. Final Thoughts
  8. Further Reading

Introduction

Have you ever found yourself tangled in a web of complex conditional logic, writing lengthy if statements that seem to go on forever? 🚦 It's a scenario that many developers encounter, especially when the business logic gets tricky. You might think that this is just part of the job, but what if I told you there's a more elegant way to handle such complexity? This is where the strategy design pattern comes into play, providing you with a clean, maintainable solution for implementing complex decisions in your code.

In this post, we’ll explore how the strategy pattern can transform complex conditional logic into a set of interchangeable algorithms, allowing for cleaner code and seamless scalability. Think of it as organizing your wardrobe; instead of tossing clothes into a pile (i.e., complex if statements), you neatly sort them by style, color, and season. This method will not only save you time but also make it easier to add or remove items in the future.

We'll dive into how to implement the strategy pattern in PHP, discussing its advantages and providing a practical example. By the end, you'll be able to apply this pattern with confidence in your own projects.


Problem Explanation

The problems with deeply nested if statements are two-fold. First, they can make your code hard to read and maintain. A method intended to execute a specific task can quickly morph into a labyrinth of logic, making it a nightmare for anyone trying to understand or modify the code later. Additionally, adding new conditions often requires rewriting large segments of code, which is both time-consuming and prone to introducing bugs.

Here's a conventional approach using if statements:

function discountCalculator($customerType, $purchaseAmount) {
    if ($customerType == 'new') {
        if ($purchaseAmount > 100) {
            return $purchaseAmount * 0.1; // 10% discount for new customers over $100
        } else {
            return $purchaseAmount * 0.05; // 5% discount otherwise
        }
    } elseif ($customerType == 'returning') {
        if ($purchaseAmount > 100) {
            return $purchaseAmount * 0.15; // 15% discount for returning customers over $100
        } else {
            return $purchaseAmount * 0.1; // 10% discount otherwise
        }
    } else {
        return 0; // No discount for unknown customer types
    }
}

As you can see, this function quickly grows unwieldy. What happens if we want to add more customer types or different discount tiers? You end up with a tangled mass of conditional statements that can obfuscate the purpose of your code.


Solution with Code Snippet

Enter the strategy design pattern, which enables you to define a family of algorithms, encapsulate each one, and make them interchangeable. This way, you can separate the algorithm implementation from the conditional logic itself, keeping your codebase clean and easy to adapt.

Here’s how we can design a discount calculator using the strategy pattern:

// Define the Strategy interface
interface DiscountStrategy {
    public function calculateDiscount($purchaseAmount);
}

// Implement concrete strategies
class NewCustomerDiscount implements DiscountStrategy {
    public function calculateDiscount($purchaseAmount) {
        return $purchaseAmount > 100 ? $purchaseAmount * 0.1 : $purchaseAmount * 0.05;
    }
}

class ReturningCustomerDiscount implements DiscountStrategy {
    public function calculateDiscount($purchaseAmount) {
        return $purchaseAmount > 100 ? $purchaseAmount * 0.15 : $purchaseAmount * 0.1;
    }
}

// Context class to use the strategies
class DiscountContext {
    private $strategy;

    public function setStrategy(DiscountStrategy $strategy) {
        $this->strategy = $strategy;
    }

    public function calculate($purchaseAmount) {
        return $this->strategy->calculateDiscount($purchaseAmount);
    }
}

// Usage
$context = new DiscountContext();
$context->setStrategy(new NewCustomerDiscount());
echo "New Customer Discount: " . $context->calculate(120) . "\n"; // Outputs 12

$context->setStrategy(new ReturningCustomerDiscount());
echo "Returning Customer Discount: " . $context->calculate(120) . "\n"; // Outputs 18

Explanation

  1. Strategy Interface: We define a DiscountStrategy interface that requires a calculateDiscount method.
  2. Concrete Strategies: Implement different discount strategies (NewCustomerDiscount and ReturningCustomerDiscount), each encapsulated in its own class.
  3. Context Class: The DiscountContext class is responsible for using a particular strategy. It contains a method to set the strategy and a method to calculate the discount based on the strategy.
  4. Usage: The client sets the appropriate strategy and requests the discount calculation. This could easily extend to many more strategies, with minimal changes to the existing code.

Adopting this design not only keeps your code cleaner, but also encourages the Open/Closed Principle—your code is open for extension but closed for modification.


Practical Application

The strategy pattern is particularly useful in scenarios where your logic varies based on distinct conditions, such as discount calculations, shipping methods, or payment processing. Wherever you see a long list of if statements trying to handle various scenarios, consider the strategy pattern as a cleaner, more modular solution.

For example, in an e-commerce application, you might have different shipping methods: standard shipping, express shipping, and overnight shipping. You can define each shipping method as a strategy, which keeps the switching logic in one place and extracts the details of each method into their individual classes.


Potential Drawbacks and Considerations

While the strategy pattern is powerful, there are scenarios where it might not be the best fit. For instance, if the number of strategies is very small (e.g., only two or three), the overhead of defining multiple classes may not justify the benefits.

Additionally, if the logic isn't likely to change much and the conditional branches are simple, it might be more expeditious to use straightforward conditional statements. That said, refactoring an existing codebase to use strategies could pay off in the long run as the application evolves.

In any case, consider your project's long-term requirements when deciding whether to apply this pattern. Early adoption might save you from a messy entanglement as your application grows.


Conclusion

Using the strategy design pattern effectively allows developers to manage complex decision-making logic in a structured way, making code more readable and maintainable. Not only do you free your logic from the confines of bulky conditional statements, but you also embrace a mindset of modular design that can dynamically evolve with your application.

By encapsulating varying behaviors in their own classes, you open the door to easily adding or removing methodologies without disrupting existing functionality. This promotes clean code and scalability, benefiting both current and future development efforts.


Final Thoughts

I encourage you to dive into the strategy design pattern and consider how it can clean up your own codebases. 🌟 Feel free to experiment and share your results or any adaptations you find effective. What other design patterns have you found useful in keeping your code clean and efficient?

If you found this post insightful, don't forget to subscribe for more expert tips and tricks to elevate your development skills!


Further Reading


Focus Keyword: Strategy Design Pattern
Related Keywords: PHP design patterns, clean code in PHP, discount calculator pattern, object-oriented PHP design patterns, PHP conditional logic alternatives