1. 模式定义
模板方法模式又叫模板模式,该模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
- 主要的方法定义为
final,防止子类修改算法骨架 - 子类必须实现的方法定义为
abstract - 普通的方法(无
final或abstract修饰)则称之为钩子(hook)
模板方法模式是基于继承的代码复用技术,模板方法模式的结构和用法也是面向对象设计的核心之一。
2. UML类图

image.png
3. 示例代码
Journey.php 父类:算法的骨架
<?php
namespace DesignPattern\Behavioral\TemplateMethod;
abstract class Journey
{
/**
* 该方法是父类和子类提供的公共服务
* 注意到方法前加了final,意味着子类不能重写该方法
*/
final public function takeATrip()
{
$this->buyAFlight();
$this->takePlane();
$this->enjoyVacation();
$this->buyGift();
$this->takePlane();
}
/**
* 该方法必须被子类实现, 这是模板方法模式的核心特性
*/
abstract protected function enjoyVacation();
/**
* 这个方法也是算法的一部分,但是是可选的,只有在需要的时候才去重写它
*/
protected function buyGift()
{
}
/**
* 子类不能访问该方法
*/
private function buyAFlight()
{
echo "Buying a flight\n";
}
/**
* 这也是个final方法
*/
final protected function takePlane()
{
echo "Taking the plane\n";
}
}
BeachJourney.php 子类
<?php
namespace DesignPattern\Behavioral\TemplateMethod;
/**
* BeachJourney类(在海滩度假)
*/
class BeachJourney extends Journey
{
protected function enjoyVacation()
{
echo "Swimming and sun-bathing\n";
}
}
CityJourney 子类
<?php
namespace DesignPattern\Behavioral\TemplateMethod;
/**
* CityJourney类(在城市中度假)
*/
class CityJourney extends Journey
{
protected function enjoyVacation()
{
echo "Eat, drink, take photos and sleep\n";
}
}
单元测试
<?php
namespace DesignPattern\Tests;
use DesignPattern\Behavioral\TemplateMethod\BeachJourney;
use DesignPattern\Behavioral\TemplateMethod\CityJourney;
use PHPUnit\Framework\TestCase;
/**
* 模板方法测试
*/
class TemplateMethodTest extends TestCase
{
public function testBeach()
{
$journey = new BeachJourney();
$this->expectOutputRegex('#sun-bathing#');
$journey->takeATrip();
}
public function testCity()
{
$journey = new CityJourney();
$this->expectOutputRegex('#drink#');
$journey->takeATrip();
}
/**
* 在PHPUnit中如何测试抽象模板方法
*/
public function testLasVegas()
{
$journey = $this->getMockForAbstractClass('DesignPattern\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";
}
}
参考文档:https://laravelacademy.org/post/3006
教程源码:https://github.com/SylviaYuan1995/DesignPatternDemo