Reduce Code Duplication in PHP with Strategy Pattern

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

Reduce Code Duplication in PHP with Strategy Pattern
Photo courtesy of ThisisEngineering

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

As a developer, you've probably experienced that developers' bane: duplicated code. This ever-looming specter often leads to hair-pulling frustration when you're debugging an application or implementing a new feature. You create a function or method, think it's been thoroughly vetted, only to revisit it weeks later and realize half your codebase has copies of it sprawled around! 😱 It’s enough to make even the most seasoned coder despair.

But what if there was a method that helps you consolidate those pesky duplicates and ensures your code remains DRY (Don’t Repeat Yourself)? Enter the Strategy Pattern! This design pattern not only enhances the organization of your code but also significantly improves its flexibility and maintainability. It effectively separates algorithms from the context, allowing you to swap these algorithms in and out with great ease.

In this blog post, we’ll dive deep into the Strategy Pattern, illustrating how you can apply this powerful technique to your PHP applications—especially in your Laravel projects. We'll explore its structure, and provide code snippets to solidify your understanding. By the end, challenges of redundancy and maintenance should be a thing of the past! Let’s get started! 🚀


Problem Explanation

You've implemented a simple web application for a local bakery. Initially, it seems straightforward. All the pricing calculations can be done in one central location. For example, you might have functions like calculateRegularBreadPrice(), calculateCroissantPrice(), and calculateCustomCakePrice(). However, as new features get added, you find yourself rewriting similar pieces of code for discount calculations, applying special prices on holidays, and offering bulk purchase discounts.

Here’s a sneaky snippet of what that initial codebase might look like:

function calculateRegularBreadPrice($quantity) {
    if ($quantity > 10) {
        return $quantity * 2.00 * 0.9; // 10% discount for bulk
    }
    return $quantity * 2.00;
}

function calculateCroissantPrice($quantity) {
    if ($quantity > 5) {
        return $quantity * 1.50 * 0.85; // 15% discount for bulk
    }
    return $quantity * 1.50;
}

// More pricing functions...

You quickly realize that these methods are now scattered all over your application. It’s repetitive, error-prone, and makes it difficult to adjust prices later. Should you need more discounts or another type of pricing rule, you'll end up hacking together yet another function, leading to even more redundant code.

But do not fret! This is precisely where the Strategy Pattern comes into play. With this design pattern, you can encapsulate these pricing calculations into reusable strategies that responsibly handle all the overhead for you.


Solution with Code Snippet

The Strategy Pattern allows you to define a family of algorithms, encapsulate each one of them, and make them interchangeable. In your bakery application, you can create a PricingStrategy interface that defines the contract for calculating prices, and then implement that interface for every pricing strategy.

Here's how you can structure it:

Step 1: Create the PricingStrategy Interface

interface PricingStrategy {
    public function calculatePrice($quantity);
}

Step 2: Implement Concrete Strategies

Now, let's implement the pricing strategies for bread and croissants:

class RegularBreadPrice implements PricingStrategy {
    public function calculatePrice($quantity) {
        if ($quantity > 10) {
            return $quantity * 2.00 * 0.9; // 10% discount
        }
        return $quantity * 2.00;
    }
}

class CroissantPrice implements PricingStrategy {
    public function calculatePrice($quantity) {
        if ($quantity > 5) {
            return $quantity * 1.50 * 0.85; // 15% discount
        }
        return $quantity * 1.50;
    }
}

Step 3: Create a Context for Pricing

Now, create a context that will use our strategies:

class PricingContext {
    private $pricingStrategy;

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

    public function calculate($quantity) {
        return $this->pricingStrategy->calculatePrice($quantity);
    }
}

Step 4: Use the Context to Execute Strategies

Finally, you can use this setup in your application seamlessly:

$pricingContext = new PricingContext();

$prices = [
    'bread' => $pricingContext->setStrategy(new RegularBreadPrice())->calculate(12),
    'croissant' => $pricingContext->setStrategy(new CroissantPrice())->calculate(6),
];

echo "Bread Price (bulk purchase): " . $prices['bread'] . "\n";
echo "Croissant Price (bulk purchase): " . $prices['croissant'] . "\n";

This setup leads to improved maintainability. Should new pricing strategies arise, you only need to implement new classes without touching the existing codebase.

Key Benefits:

  • Flexibility: Easily swap out and extend strategies without altering the context.
  • Maintainability: Centralizes the calculations for greater organization and reduced duplication.
  • Testability: Each pricing strategy can be unit tested independently.

Practical Application

The Strategy Pattern shines particularly in scenarios involving complex business rules requiring various strategies. Whether it's a pricing engine, sorting algorithms, or validation checks, this pattern is versatile.

Imagine your bakery app is growing into a cafe. You may want to introduce new cakes, smoothies, and other beverages. With the Strategy Pattern, you can quickly add strategies for these new items without rewriting existing code.

Furthermore, you could even implement caching strategies for these calculations, allowing you to enhance performance while maintaining an elegant architecture.

Example Use Case: Special Events Discounts

For example, if you want to include an additional discount for holidays or bulk orders, you can simply create a HolidayDiscountStrategy:

class HolidayDiscountStrategy implements PricingStrategy {
    public function calculatePrice($quantity) {
        return $quantity * 1.50 * 0.8; // 20% holiday discount
    }
}

// Using the new strategy
$pricingContext->setStrategy(new HolidayDiscountStrategy())->calculate(5);

The new strategy can be implemented and tested without disrupting the existing codebase.


Potential Drawbacks and Considerations

While the Strategy Pattern is extremely useful for managing complexity, it has some drawbacks. Creating individual strategy classes can lead to an explosion of classes in larger applications. If a pricing strategy has a lot of parameters or rules, it could also make maintenance challenging.

To mitigate this, consider organizing related strategies in separate folders to maintain structure and possibly using a factory to manage the creation of these strategies. This keeps the overhead low and provides clarity.


Conclusion

Incorporating the Strategy Pattern into your PHP applications is like giving your codebase a health check-up—it promotes cleaner code and reduces redundancy. It allows you to manage algorithms effortlessly while keeping your focus on the application's core logic and features. You no longer have to be that programmer spending endless hours hunting for duplicated calculations or troublesome functions. Instead, embrace the organization and flexibly adapt!

Key Takeaways:

  • Encapsulates algorithms into independent classes for better manageability.
  • Increases the flexibility of code changes without impacting the overall structure.
  • Enhances the scalability of applications as new strategies can be easily introduced.

Final Thoughts

Have you experimented with the Strategy Pattern in your recent projects? Why not give it a try the next time you're faced with code duplication or resetting algorithm logic! I encourage you to share your experiences or alternative strategies in the comments below! 💬

If you found this guide helpful, please subscribe for more posts that help elevate your development game! Your feedback drives great content, so let’s hear from you!


Further Reading

  1. Design Patterns in PHP - The Strategy Pattern
  2. PHP Object-Oriented Programming by Matt Doyle
  3. Refactoring Guru - Strategy Pattern

Focus Keyword: Strategy Pattern in PHP

Related Keywords: Design patterns in PHP, PHP OOP principles, Laravel code optimization, Maintainable PHP applications, Pricing strategies in software.