模板方法模式

目的

模板方法是一种行为设计模式。

你可能已经遇到过多次使用模板方法的场景,其核心思想是让抽象模板的子类来完成具体的算法。

这也是“好莱坞原则”:不要打电话给我们,我们会叫你。模板类不是被子类调用,而正好相反。怎么调用?当然是用抽象。

换句话说,这是一个算法骨架,很适合在框架和库代码中使用。用户只需实现指定的方法,父类就可以完成工作。

这是一种简单的解耦具体类的方法,减少了“复制/粘贴”行为,这就是为什么你会发现它随处可见。

UML 类图

模板方法模式类图

代码

Journey.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
<?php declare(strict_types = 1);

namespace DesignPatterns\Behavioral\TemplateMethod;

abstract class Journey
{
/**
* @var string[]
*/
private array $thingsToDo = [];

/**
* This is the public service provided by this class and its subclasses.
* Notice it is final to "freeze" the global behavior of algorithm.
* If you want to override this contract, make an interface with only takeATrip()
* and subclass it.
*/
final public function takeATrip()
{
$this->thingsToDo[] = $this->buyAFlight();
$this->thingsToDo[] = $this->takePlane();
$this->thingsToDo[] = $this->enjoyVacation();
$buyGift = $this->buyGift();

if ($buyGift !== null) {
$this->thingsToDo[] = $buyGift;
}

$this->thingsToDo[] = $this->takePlane();
}

/**
* This method must be implemented, this is the key-feature of this pattern.
*/
abstract protected function enjoyVacation(): string;

/**
* This method is also part of the algorithm but it is optional.
* You can override it only if you need to
*/
protected function buyGift(): ?string
{
return null;
}

private function buyAFlight(): string
{
return 'Buy a flight ticket';
}

private function takePlane(): string
{
return 'Taking the plane';
}

/**
* @return string[]
*/
public function getThingsToDo(): array
{
return $this->thingsToDo;
}
}

BeachJourney.php

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

namespace DesignPatterns\Behavioral\TemplateMethod;

class BeachJourney extends Journey
{
protected function enjoyVacation(): string
{
return "Swimming and sun-bathing";
}
}

CityJourney.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\TemplateMethod;

class CityJourney extends Journey
{
protected function enjoyVacation(): string
{
return "Eat, drink, take photos and sleep";
}

protected function buyGift(): ?string
{
return "Buy a gift";
}
}

测试

Tests/JourneyTest.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
<?php declare(strict_types = 1);

namespace DesignPatterns\Behavioral\TemplateMethod\Tests;

use DesignPatterns\Behavioral\TemplateMethod\BeachJourney;
use DesignPatterns\Behavioral\TemplateMethod\CityJourney;
use PHPUnit\Framework\TestCase;

class JourneyTest extends TestCase
{
public function testCanGetOnVacationOnTheBeach()
{
$beachJourney = new BeachJourney();
$beachJourney->takeATrip();

$this->assertSame(
['Buy a flight ticket', 'Taking the plane', 'Swimming and sun-bathing', 'Taking the plane'],
$beachJourney->getThingsToDo()
);
}

public function testCanGetOnAJourneyToACity()
{
$cityJourney = new CityJourney();
$cityJourney->takeATrip();

$this->assertSame(
[
'Buy a flight ticket',
'Taking the plane',
'Eat, drink, take photos and sleep',
'Buy a gift',
'Taking the plane'
],
$cityJourney->getThingsToDo()
);
}
}