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 Diagram3.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}