3.11. Template Method

3.11.1. Purpose

Template Method is a behavioral design pattern.

Perhaps you have encountered it many times already. The idea is to let subclasses of this abstract template “finish” the behavior of an algorithm.

A.k.a the “Hollywood principle”: “Don’t call us, we call you.” This class is not called by subclasses but the inverse. How? With abstraction of course.

In other words, this is a skeleton of algorithm, well-suited for framework libraries. The user has just to implement one method and the superclass do the job.

It is an easy way to decouple concrete classes and reduce copy-paste, that’s why you’ll find it everywhere.

3.11.2. UML Diagram

Alt TemplateMethod UML Diagram

3.11.3. Code

You can also find these code on GitHub

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

namespace DesignPatterns\Behavioral\TemplateMethod;

/**
 *
 */
abstract class Journey
{
    /**
     * 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->buyAFlight();
        $this->takePlane();
        $this->enjoyVacation();
        $this->buyGift();
        $this->takePlane();
    }

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

    /**
     * This method is also part of the algorithm but it is optional.
     * This is an "adapter" (do not confuse with the Adapter pattern, not related)
     * You can override it only if you need to.
     */
    protected function buyGift()
    {
    }

    /**
     * This method will be unknown by subclasses (better)
     */
    private function buyAFlight()
    {
        echo "Buying a flight\n";
    }

    /**
     * Subclasses will get access to this method but cannot override it and
     * compromise this algorithm (warning : cause of cyclic dependencies)
     */
    final protected function takePlane()
    {
        echo "Taking the plane\n";
    }

    // A note regarding the keyword "final" : don't use it when you start coding :
    // add it after you narrow and know exactly what change and what remain unchanged
    // in this algorithm.
    // [abstract] x [3 access] x [final] = 12 combinations, it can be hard !
}

BeachJourney.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?php

namespace DesignPatterns\Behavioral\TemplateMethod;

/**
 * BeachJourney is vacation at the beach
 */
class BeachJourney extends Journey
{
    /**
     * prints what to do to enjoy your vacation
     */
    protected function enjoyVacation()
    {
        echo "Swimming and sun-bathing\n";
    }
}

CityJourney.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?php

namespace DesignPatterns\Behavioral\TemplateMethod;

/**
 * CityJourney is a journey in a city
 */
class CityJourney extends Journey
{
    /**
     * prints what to do in your journey to enjoy vacation
     */
    protected function enjoyVacation()
    {
        echo "Eat, drink, take photos and sleep\n";
    }
}

3.11.4. Test

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
39
40
41
42
43
44
<?php

namespace DesignPatterns\Behavioral\TemplateMethod\Tests;

use DesignPatterns\Behavioral\TemplateMethod;

/**
 * JourneyTest tests all journeys
 */
class JourneyTest extends \PHPUnit_Framework_TestCase
{

    public function testBeach()
    {
        $journey = new TemplateMethod\BeachJourney();
        $this->expectOutputRegex('#sun-bathing#');
        $journey->takeATrip();
    }

    public function testCity()
    {
        $journey = new TemplateMethod\CityJourney();
        $this->expectOutputRegex('#drink#');
        $journey->takeATrip();
    }

    /**
     * How to test an abstract template method with PHPUnit
     */
    public function testLasVegas()
    {
        $journey = $this->getMockForAbstractClass('DesignPatterns\Behavioral\TemplateMethod\Journey');
        $journey->expects($this->once())
            ->method('enjoyVacation')
            ->will($this->returnCallback(array($this, 'mockUpVacation')));
        $this->expectOutputRegex('#Las Vegas#');
        $journey->takeATrip();
    }

    public function mockUpVacation()
    {
        echo "Fear and loathing in Las Vegas\n";
    }
}