3.12. Template Method

3.12.1. Propósito

Template Method es un patrón de diseño de comportamiento.

Quizás ya lo haya encontrado muchas veces. La idea es permitir que las subclases de esta plantilla abstracta «terminen» el comportamiento de un algoritmo.

También conocido como el «el pirincipio de Hollywood»: «No nos llame, nosotros le llamaremos.» Esta clase no es llamada por subclases, sino a la inversa. ¿Cómo? Con abstracción por supuesto.

En otras palabras, se trata de un esqueleto de algoritmo, muy adecuado para bibliotecas de marcos. El usuario sólo tiene que implementar un método y la superclase hace el trabajo.

Es una forma fácil de desacoplar clases concretas y reducir el copiado y pegado, por eso lo encontrará en todas partes.

3.12.2. Diagrama UML

Alt TemplateMethod UML Diagram

3.12.3. Código

Puedes encontrar el código en 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. Test

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}