3.11. Strategy

3.11.1. Terminology:

  • Context

  • Strategy

  • Concrete Strategy

3.11.2. 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).

3.11.3. 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

3.11.4. UML Diagram

Alt Strategy UML Diagram

3.11.5. Code

You can also find this code on GitHub

Context.php

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace DesignPatterns\Behavioral\Strategy;
 6
 7class Context
 8{
 9 public function __construct(private Comparator $comparator)
10 {
11 }
12
13 public function executeStrategy(array $elements): array
14 {
15 uasort($elements, [$this->comparator, 'compare']);
16
17 return $elements;
18 }
19}

Comparator.php

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace DesignPatterns\Behavioral\Strategy;
 6
 7interface Comparator
 8{
 9 /**
10 * @param mixed $a
11 * @param mixed $b
12 */
13 public function compare($a, $b): int;
14}

DateComparator.php

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace DesignPatterns\Behavioral\Strategy;
 6
 7use DateTime;
 8
 9class DateComparator implements Comparator
10{
11 public function compare($a, $b): int
12 {
13 $aDate = new DateTime($a['date']);
14 $bDate = new DateTime($b['date']);
15
16 return $aDate <=> $bDate;
17 }
18}

IdComparator.php

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace DesignPatterns\Behavioral\Strategy;
 6
 7class IdComparator implements Comparator
 8{
 9 public function compare($a, $b): int
10 {
11 return $a['id'] <=> $b['id'];
12 }
13}

3.11.6. Test

Tests/StrategyTest.php

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace DesignPatterns\Behavioral\Strategy\Tests;
 6
 7use DesignPatterns\Behavioral\Strategy\Context;
 8use DesignPatterns\Behavioral\Strategy\DateComparator;
 9use DesignPatterns\Behavioral\Strategy\IdComparator;
10use PHPUnit\Framework\TestCase;
11
12class StrategyTest extends TestCase
13{
14 public function provideIntegers()
15 {
16 return [
17 [
18 [['id' => 2], ['id' => 1], ['id' => 3]],
19 ['id' => 1],
20 ],
21 [
22 [['id' => 3], ['id' => 2], ['id' => 1]],
23 ['id' => 1],
24 ],
25 ];
26 }
27
28 public function provideDates()
29 {
30 return [
31 [
32 [['date' => '2014年03月03日'], ['date' => '2015年03月02日'], ['date' => '2013年03月01日']],
33 ['date' => '2013年03月01日'],
34 ],
35 [
36 [['date' => '2014年02月03日'], ['date' => '2013年02月01日'], ['date' => '2015年02月02日']],
37 ['date' => '2013年02月01日'],
38 ],
39 ];
40 }
41
42 /**
43 * @dataProvider provideIntegers
44 *
45 * @param array $collection
46 * @param array $expected
47 */
48 public function testIdComparator($collection, $expected)
49 {
50 $obj = new Context(new IdComparator());
51 $elements = $obj->executeStrategy($collection);
52
53 $firstElement = array_shift($elements);
54 $this->assertSame($expected, $firstElement);
55 }
56
57 /**
58 * @dataProvider provideDates
59 *
60 * @param array $collection
61 * @param array $expected
62 */
63 public function testDateComparator($collection, $expected)
64 {
65 $obj = new Context(new DateComparator());
66 $elements = $obj->executeStrategy($collection);
67
68 $firstElement = array_shift($elements);
69 $this->assertSame($expected, $firstElement);
70 }
71}