Strategy Pattern

To separate strategies and to enable fast switching between them. Also this pattern is a good alternative to inheritance (instead of having an abstract class that is extended).


Purpose

To separate strategies and to enable fast switching between them. Also this pattern is a good alternative to inheritance (instead of having an abstract class that is extended).

Essentially by passing an object into the construct (it’s recommended that you type hint to an interface) you can 'execute a strategy' (i.e do something) on the given object using data passed in via methods parameters.

Examples
  • Sorting a list of objects, one strategy by date, the other by id
  • Simplify unit testing: e.g. switching between file and in-memory storage
UML


Code

Comparator


namespace DesignPatterns\Behavioral\Strategy;

interface Comparator
{
  /**
  * @param mixed $a
  * @param mixed $b
  */
  public function compare($a, $b): int; 

}

DateComparator


namespace DesignPatterns\Behavioral\Strategy;

use DateTime;

class DateComparator implements Comparator
{
    public function compare($a, $b): int
    {
        $aDate = new DateTime($a['date']);
        $bDate = new DateTime($b['date']);

        return $aDate <=> $bDate;
    }
}

IdComparator


namespace DesignPatterns\Behavioral\Strategy;

class IdComparator implements Comparator
{
    public function compare($a, $b): int
    {
        return $a['id'] <=> $b['id'];
    }
}

Context


namespace DesignPatterns\Behavioral\Strategy;

class Context
{
    public function __construct(private Comparator $comparator)
    {
    }

    public function executeStrategy(array $elements): array
    {
        uasort($elements, [$this->comparator, 'compare']);

        return $elements;
    }
}

Tests


public function provideIntegers()
{
    return [
        [
            [['id' => 2], ['id' => 1], ['id' => 3]],
            ['id' => 1],
        ],
        [
            [['id' => 3], ['id' => 2], ['id' => 1]],
            ['id' => 1],
        ],
    ];
}

public function provideDates()
{
    return [
        [
            [['id' => 2], ['id' => 1], ['id' => 3]],
            ['id' => 1],
        ],
        [
            [['id' => 3], ['id' => 2], ['id' => 1]],
            ['id' => 1],
        ],
    ];
}

/**
* @dataProvider provideIntegers
*
* @param array $collection
* @param array $expected
*/
public function testIdComparator($collection, $expected)
{
    $obj = new Context(new IdComparator());
    $elements = $obj->executeStrategy($collection);

    $firstElement = array_shift($elements);
    $this->assertSame($expected, $firstElement);
}

/**
* @dataProvider provideDates
* 
* @param array $collection
* @param array $expected
*/
public function testDateComparator($collection, $expected)
{
    $obj = new Context(new DateComparator());
    $elements = $obj->executeStrategy($collection);

    $firstElement = array_shift($elements);
    $this->assertSame($expected, $firstElement);
}