The Strategy Pattern

1. Definition

The strategy pattern defines a family of algorithms, encapsulate each one, and make them interchangeable

2. Case study

2.1 The Problem

A couple of years ago, you started a restaurant. Your restaurant had become so successful that you started to hire employees.

Your employee’s payments were determined by …

Wage rate × working hours 

Their wage rates were determined by their seniority (junior, senior etc).

Each employee had to submit this timesheet every 2 weeks.

Once you received timesheets from all the employees, you (or someone else) had to calculate the payments and record them.

Because it was tedious and error-prone to calculate those payments manually, you decided to automate it by introducing a web app.

In the app, you just needed to multiply those values in the following way to calculate the payment amount.

class Staff
{
    private $seniority;

    public function getWage($hours) {
        if ($this->seniority === "junior") {
            return $hours * 20;
        }else ($this->seniority === "senior") {
            return $hours * 25;
        }
        //error handling
    }
}

5 years later…

Your restaurant had become quite popular and you started to run several other restaurants under the same name in your city.

One day, however, some of the employees who worked as leaders claimed for compensation if they worked for more than 8 hours on a day, due to the amount of work they handle.

After careful consideration, you decided to accept their claim.

The Staff class was updated in the following way.

abstract class Staff
{
    protected $wageRate;

    abstract public function getWage($hours);
}

class JuniorEmployee extends Staff
{
    public function __construct() 
    {
        $this->wageRate = 20;
    }

    public function getWage($hours){
        return $hours * $this->wageRate;
    }
}

class SeniorEmployee extends Staff
{
    //The same implementation as JuniorEmployee
    //except the wage rate
}

class Leader extends Staff
{
    private $leaderBounus;
    public function __construct() 
    {
        $this->wageRate     = 25;
        $this->leaderBounus = 40;
    }

    public function getWage($hours){
        if ($hours > 8){
            return $hours * $this->wageRate + $this->leaderBounus;
        }

        return $hours * $this->wageRate;
    }
}

10 years later…

Your hard work and business acumen had led your restaurant to be a big restaurant chain, operating across your country.

Once again, however, you faced new issues regarding payments for employees.

Firstly, you decided to determine the wage for managers of each restaurant by their monthly revenue, rather than the work hours, wage rate and compensation.

So, the app got a new class…

class Manager extends Staff
{
    public function getWage($hours){
        //Managers' earnings aren't determined
        //by how long they worked
    }
}

Secondly, because the average wage rates in your country varied from state to state (you can use “prefecture” or “province” if you like), it became recessary to set different rates for the same role based on the state.

In other words, JuniorEmployeeSeniorEmployee and Leader would need different wage rates based on the state.

So, all those classes were updated by the developer who didn’t much about design patterns..

NOTE: ACT and NSW represent Australian states.

class JuniorEmployeeInACT extends Staff
{
    public function __construct() 
    {
        $this->wageRate = 24;
    }

    public function getWage($hours){
        return $hours * $this->wageRate;
    }
}

class JuniorEmployeeInNSW extends Staff
{
    public function __construct() 
    {
        $this->wageRate = 20;
    }

    public function getWage($hours){
        return $hours * $this->wageRate;
    }
}

//Update JuniorEmployee for all the states in this way.

//Also, don't forget to do the same thing for
//senior employees and leaders.

So far, the developer of this app has solved those issues above by creating a new subclass, each time the restaurant owner had to re-consider how to determine the wage.

Let’s consider the drawbacks of this approach.

A. Code duplications

For instance, if junior employees in 5 states have the same rate, you’ll have to write the same code for the 5 classes.

(I made this point obvious in this case study for the sake of clarification… I knew few people would actually take such an approach!)

B: Updating the clients (i.e. types of workers) will be painful

What if, for example, your chain restaurants will hire interns whose wages’ won’t be determined by the working hours (just like managers)? Likewise, what if you want to set different wage rates for chefs and waitresses in addition to their seniority?

2.2 The solution

1. Identify behaviours (usually, these are functions) which vary depending on the client (i.e. classes that have the behaviours)

In our scenario, the behaviour is obviously getWageRate().

2.  Create an interface (or an abstract superclass) for the behaviours.

It depends on the context whether an interface or abstract class should be used.

The point is to make use of polymorphism, so that the concrete class doesn’t have to know about how the behaviour is implemented while the concrete class can behave accordingly.

interface WageStrategy
{
    public function getWage($hours);
}

3. Create implementations for the behaviours

In our case, some of the implementations will look like…

class WageA implements WageStrategy
{
    public function getWage($hours){
        return $hours * 20;
    }
}

class WageB implements WageStrategy
{
    public function getWage($hours){
        if ($hours > 8){
            return $hours * 25 + 40;
        }

        return $hours * $this->wageRate;
    }
}
//In the real world, of course we should use more
//sensible class names, rather than just WageA or B

4. Add an instance variable to the client(s) and put the instance variable to their constructors

In our scenario, we could actually remove all the subclasses as long as doing so doesn’t affect other parts of this app.

class Staff
{
    private $wageStrategy;

    public function setWageStrategy($wageStrategy)
    {
        $this->wageStrategy = $wageStrategy;
    }

    public function getWage($hours)
    {
        return $this->wageStrategy->getWage($hours);
    }
}

5. Define a setter function, so that the behaviour can be updated on runtime

 In the picture above, setWageStrategy($wageStrategy) is the setter.

6. Implement the behaviour via the instance variable 


$s = new Staff();

$s->setWageStrategy(new WageA());
$s->getWage(10)
// 200

$s->setWageStrategy(new WageB());
$s->getWage(10)
// 290 (i.e. 25 * 10 + 40) 

2.3 The outcome

As you can see, applying the Strategy pattern not only eliminated all the subclasses but also allowed us to reuse the same code.

Also, instead of having a subclass for each type of staff (junior, senior, leader etc), this app ended up having only one client (Staff class), which determines the wage on runtime with the setter function.

3. The structure

4. When to use this pattern

When you have to define certain behaviours to multiple instances (or clients) without code duplication, and/or you have to change certain behaviours on runtime.

 

Leave a Reply

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