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 letsubclasses of this abstract template “finish” the behavior of analgorithm.

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

In other words, this is a skeleton of algorithm, well-suited forframework libraries. The user has just to implement one method and thesuperclass 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 this code on GitHub

Journey.php

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

BeachJourney.php

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

CityJourney.php

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

3.11.4. Test

Tests/JourneyTest.php

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