策略模式

术语

  • Context:上下文
  • Strategy:策略
  • Concrete Strategy:具体策略

目的

分离不同的策略,并实现各种策略之间的快速切换。另外,策略模式也是继承的一个很好的替代方案(而不是拥有一个被扩展的抽象类)。

使用场景

  • 对一组对象进行排序,一种按时间排序,其余的按 ID 排序
  • 简化单元测试:比如,在文件存储和内存存储之间切换时

UML 类图

策略模式类图

代码

Context.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php declare(strict_types = 1);

namespace DesignPatterns\Behavioral\Strategy;

class Context
{
private Comparator $comparator;

public function __construct(Comparator $comparator)
{
$this->comparator = $comparator;
}

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

return $elements;
}
}

Comparator.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php declare(strict_types = 1);

namespace DesignPatterns\Behavioral\Strategy;

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

DateComparator.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php declare(strict_types = 1);

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

1
2
3
4
5
6
7
8
9
10
11
<?php declare(strict_types = 1);

namespace DesignPatterns\Behavioral\Strategy;

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

测试

Tests/StrategyTest.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<?php declare(strict_types = 1);

namespace DesignPatterns\Behavioral\Strategy\Tests;

use DesignPatterns\Behavioral\Strategy\Context;
use DesignPatterns\Behavioral\Strategy\DateComparator;
use DesignPatterns\Behavioral\Strategy\IdComparator;
use PHPUnit\Framework\TestCase;

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

public function provideDates()
{
return [
[
[['date' => '2014-03-03'], ['date' => '2015-03-02'], ['date' => '2013-03-01']],
['date' => '2013-03-01'],
],
[
[['date' => '2014-02-03'], ['date' => '2013-02-01'], ['date' => '2015-02-02']],
['date' => '2013-02-01'],
],
];
}

/**
* @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);
}
}