3.12. Шаблонен метод

3.12.1. Предназначение

Шаблонен метод е поведенчески шаблон за дизайн.

Може би вече сте го срещали много пъти. Идеята е да се остави подкласовете на този абстрактен шаблон да „довършат“ поведението на алгоритъм.

А.к.а „холивудският принцип“ („Hollywood principle“): „Не ни се обаждайте, ние ще ви се обадим“. Този клас не се извиква от подкласове, а обратно. Как? С абстракция разбира се.

С други думи, това е скелет на алгоритъм, подходящ за фреймуърк библиотеки. Потребителят трябва само да напиша един метод и суперкласът свърши работата.

Това е лесен начин да отделите конкретни класове и да намалите copy-paste, затова ще го намерите навсякъде.

3.12.2. UML Диаграма

Alt TemplateMethod UML Diagram

3.12.3. Код

Можете също да намерите този код в GitHub

Journey.php

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace DesignPatterns\Behavioral\TemplateMethod;
 6
 7abstract class Journey
 8{
 9    /**
10     * @var string[]
11     */
12    private array $thingsToDo = [];
13
14    /**
15     * This is the public service provided by this class and its subclasses.
16     * Notice it is final to "freeze" the global behavior of algorithm.
17     * If you want to override this contract, make an interface with only takeATrip()
18     * and subclass it.
19     */
20    final public function takeATrip()
21    {
22        $this->thingsToDo[] = $this->buyAFlight();
23        $this->thingsToDo[] = $this->takePlane();
24        $this->thingsToDo[] = $this->enjoyVacation();
25        $buyGift = $this->buyGift();
26
27        if ($buyGift !== null) {
28            $this->thingsToDo[] = $buyGift;
29        }
30
31        $this->thingsToDo[] = $this->takePlane();
32    }
33
34    /**
35     * This method must be implemented, this is the key-feature of this pattern.
36     */
37    abstract protected function enjoyVacation(): string;
38
39    /**
40     * This method is also part of the algorithm but it is optional.
41     * You can override it only if you need to
42     */
43    protected function buyGift(): ?string
44    {
45        return null;
46    }
47
48    private function buyAFlight(): string
49    {
50        return 'Buy a flight ticket';
51    }
52
53    private function takePlane(): string
54    {
55        return 'Taking the plane';
56    }
57
58    /**
59     * @return string[]
60     */
61    final public function getThingsToDo(): array
62    {
63        return $this->thingsToDo;
64    }
65}

BeachJourney.php

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace DesignPatterns\Behavioral\TemplateMethod;
 6
 7class BeachJourney extends Journey
 8{
 9    protected function enjoyVacation(): string
10    {
11        return "Swimming and sun-bathing";
12    }
13}

CityJourney.php

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace DesignPatterns\Behavioral\TemplateMethod;
 6
 7class CityJourney extends Journey
 8{
 9    protected function enjoyVacation(): string
10    {
11        return "Eat, drink, take photos and sleep";
12    }
13
14    protected function buyGift(): ?string
15    {
16        return "Buy a gift";
17    }
18}

3.12.4. Тест

Tests/JourneyTest.php

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace DesignPatterns\Behavioral\TemplateMethod\Tests;
 6
 7use DesignPatterns\Behavioral\TemplateMethod\BeachJourney;
 8use DesignPatterns\Behavioral\TemplateMethod\CityJourney;
 9use PHPUnit\Framework\TestCase;
10
11class JourneyTest extends TestCase
12{
13    public function testCanGetOnVacationOnTheBeach()
14    {
15        $beachJourney = new BeachJourney();
16        $beachJourney->takeATrip();
17
18        $this->assertSame(
19            ['Buy a flight ticket', 'Taking the plane', 'Swimming and sun-bathing', 'Taking the plane'],
20            $beachJourney->getThingsToDo()
21        );
22    }
23
24    public function testCanGetOnAJourneyToACity()
25    {
26        $cityJourney = new CityJourney();
27        $cityJourney->takeATrip();
28
29        $this->assertSame(
30            [
31                'Buy a flight ticket',
32                'Taking the plane',
33                'Eat, drink, take photos and sleep',
34                'Buy a gift',
35                'Taking the plane'
36            ],
37            $cityJourney->getThingsToDo()
38        );
39    }
40}