策略模式(Strategy)

术语

  • 上下文
  • 策略
  • 具体策略

目的

分离「策略」并使他们之间能互相快速切换。此外,这种模式是一种不错的继承替代方案(替代使用扩展抽象类的方式)。

例子

  • 对一个对象列表进行排序,一种按照日期,一种按照 id
  • 简化版的的单元测试:例如,在使用文件存储和内存存储之间互相切换

UML 类图

file

代码

你可以在 GitHub 上找到这个代码。

Context.php

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

ComparatorInterface.php

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

DateComparator.php

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

IdComparator.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\Strategy;
  3. class IdComparator implements ComparatorInterface
  4. {
  5. /**
  6. * @param mixed $a
  7. * @param mixed $b
  8. *
  9. * @return int
  10. */
  11. public function compare($a, $b): int
  12. {
  13. return $a['id'] <=> $b['id'];
  14. }
  15. }

测试

Tests/StrategyTest.php

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