3.12. Template Method
3.12.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.12.2. UML Diagram
Alt TemplateMethod UML Diagram3.12.3. Code
You can also find this code on 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}