Refactoring Spaghetti into Elegance: Unraveling the Strategy Pattern

In software development, design patterns provide structured solutions to common problems. The Strategy Pattern is one such solution, especially useful for organizing code. In this post, we'll explore the benefits of the Strategy Pattern and demonstrate its application with a real-world example.

The Scenario

Imagine a student accommodation platform. Students can choose from various accommodation types: studio, ensuite, and luxury apartment. Additionally, they have the option to select amenities and decide their payment method: full payment or installments.

Here's a simplified version of what the code might look like initially:

class StudentAccommodation
{
    public function book($type, $paymentMethod)
    {
        if ($type === 'studio') {
            // logic for booking studio
        } elseif ($type === 'ensuite') {
            // logic for booking ensuite
        } elseif ($type === 'luxury') {
            // logic for booking luxury apartment
        }

        if ($paymentMethod === 'full') {
            // logic for full payment
        } else {
            // logic for installments
        }
        // ... more code for amenities and other features
    }
}

This approach, while functional, can quickly become a tangled mess as more features and conditions are added.

Enter the Strategy Pattern

The Strategy Pattern involves defining a family of algorithms, encapsulating each one, and making them interchangeable. This allows the algorithm to vary independently from clients that use it.

Let's refactor our student accommodation class using the Strategy Pattern:

First, we will define our accommodation strategy:

// accommodation strategy contract
interface AccommodationStrategy
{
    public function book();
}

class Studio implements AccommodationStrategy
{
    public function book()
    {
        // logic for booking studio
    }
}

class Ensuite implements AccommodationStrategy
{
    public function book()
    {
        // logic for booking ensuite
    }
}

class LuxuryApartment implements AccommodationStrategy
{
    public function book()
    {
        // logic for booking luxury apartment
    }
}

Then, let's define our payment strategy:

// payment strategy contract
interface PaymentStrategy
{
    public function pay();
}

class FullPayment implements PaymentStrategy
{
    public function pay()
    {
        // logic for full payment
    }
}

class Installments implements PaymentStrategy
{
    public function pay()
    {
        // logic for installments
    }
}

With our strategies set, we can now create a context class responsible for students' bookings:

class StudentAccommodation
{
    private $accommodation;
    private $payment;

    public function __construct(AccommodationStrategy $accommodation, PaymentStrategy $payment)
    {
        $this->accommodation = $accommodation;
        $this->payment = $payment;
    }

    public function book()
    {
        $this->accommodation->book();
        $this->payment->pay();
        // ... handle amenities and other features
    }
}

Benefits of the Strategy Pattern

  • Separation of Concerns: Each strategy encapsulates a specific behavior, making the code more modular.
  • Flexibility: New accommodation types or payment methods can be added without altering existing code.
  • Maintainability: Changes to a specific booking or payment logic only affect their respective strategy classes.
  • Reusability: Strategies can be reused across different parts of the application or even in different projects.

Conclusion

The Strategy Pattern is an invaluable asset for streamlining complex code. By segregating behaviors into distinct strategy classes, developers can foster a cleaner, more maintainable, and adaptable codebase. While we've showcased PHP in this example, the core concept is applicable across all programming languages.