模板方法(Template Method)模式,英文是 Template Method Design Pattern。在 GoF 的《设计模式》一书中,它是这么定义的:

Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.

翻译成中文就是:模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。这里的“算法”,我们可以理解为广义上的“业务逻辑”,并不特指数据结构和算法中的“算法”。这里的算法骨架就是“模板”,包含算法骨架的方法就是“模板方法”,这也是模板方法模式名字的由来。

模板方法模式的结构与实现

结构

  • 抽象类(Abstract Class):定义了一到多个的抽象方法,以供具体的子类来实现它们;而且还要实现一个模板方法,来定义一个算法的骨架。该模板方法不仅调用前面的抽象方法,也可以调用其他的操作,只要能完成自身的使命。如果你不想模板方法被子类重写,可用 final修饰模板方法以防止子类对其进行重写。
  • 具体类(Concrete Class):实现父类中的抽象方法以完成算法中与特定子类相关的步骤。

代码实现

AbstractClass.php

<?php
namespace DesignPatterns\TemplateMethod\example1;
/**
 * AbstractClass是抽象类,其实也就是一抽象模板, 定义并实现了一个模板方法。这个模板方法一般
 * 是一个具体方法,它给出了一个顶级逻辑的骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类
 * 实现。顶级逻辑也有可能调用一些具体方法。
 * Class AbstractClass
 * @package DesignPatterns\TemplateMethod\example1
 */
abstract class AbstractClass
{
    //定义抽象行为,在子类中实现
    abstract public function PrimitiveOperation1();
    abstract public function PrimitiveOperation2();
    
    //模板方法给出逻辑的骨架,而逻辑的组成是一些相应的抽象操作
    //它们都推迟到子类中实现
    public function TemplateMethod()
    {
        $this->PrimitiveOperation1();
        $this->PrimitiveOperation2();
    }

}

ConcreteClassA.php

<?php
namespace DesignPatterns\TemplateMethod\example1;
/**
 * ConcreteClass,实现父类所定义的一一个或多个抽象方法。每一一个AbstractClass都可以有任意多个
 * ConcreteClass与之对应,而每一一个 ConcreteClass都可以
 * 给出这些抽象方法(也就是顶级逻辑的组成步骤)的不同实现,从而使得顶级逻辑的实现各不相同。
 * Class ConcreteClassA
 * @package DesignPatterns\TemplateMethod\example1
 */
class ConcreteClassA extends AbstractClass
{

    public function PrimitiveOperation1()
    {
        echo '具体类A实现方法1',PHP_EOL;
    }

    public function PrimitiveOperation2()
    {
        echo '具体类A实现方法2',PHP_EOL;
    }
}

ConcreteClassB.php

<?php
namespace DesignPatterns\TemplateMethod\example1;
/**
 * Class ConcreteClassB
 * @package DesignPatterns\TemplateMethod\example1
 */
class ConcreteClassB extends AbstractClass
{

    public function PrimitiveOperation1()
    {
        echo '具体类B实现方法1',PHP_EOL;
    }

    public function PrimitiveOperation2()
    {
        echo '具体类B实现方法2',PHP_EOL;
    }
}

运行Client.php

<?php
namespace DesignPatterns\TemplateMethod\example1;
require dirname(__DIR__).'/../vendor/autoload.php';
/**
 * Class Client
 * @package DesignPatterns\TemplateMethod\example1
 */
class Client
{
    public function run(){
        $c = new ConcreteClassA();
        $c->TemplateMethod();
        echo "<br>";
        $c = new ConcreteClassB();
        $c->TemplateMethod();
    }
}
$worker = new Client();
$worker->run();

运行结果:

在客户端的调用中,实例化子类,但调用的是子类所继承的父类的模板方法。它决定了执行的顺序等行为只能由父级抽象类决定;而子类只需要完成具体的操作内容;

示例

在本例中, 模板方法模式定义了在社交网络上发布消息的算法框架。 每个子类都代表一个不同的社交网络, 它们虽以不同方式实现所有步骤, 但却会复用基本的算法。
SocialNetwork.php

<?php
namespace DesignPatterns\TemplateMethod\example2;
/**
 * Class SocialNetwork
 * @package DesignPatterns\TemplateMethod\example2
 */
abstract class SocialNetwork
{
    protected $username;

    protected $password;

    public function __construct(string $username, string $password)
    {
        $this->username = $username;
        $this->password = $password;
    }

    /**
     * 实际的模板方法按特定的顺序调用抽象步骤。
     * 子类可以实现所有的步骤,允许这个方法在社交网络上发布内容。
     * @param string $message
     * @return bool
     */
    public function post(string $message): bool
    {

        if ($this->logIn($this->username, $this->password)) {

            $result = $this->sendData($message);

            $this->logOut();

            return $result;
        }

        return false;
    }
    //登陆
    abstract public function logIn(string $userName, string $password): bool;
    //发布信息
    abstract public function sendData(string $message): bool;
    //退出
    abstract public function logOut(): void;
    //一个小助手方法,让等待时间感觉真实。
    protected function simulateNetworkLatency()
    {
        $i = 0;
        while ($i < 5) {
            echo ".";
            sleep(1);
            $i++;
        }
    }
}

Facebook.php

<?php
namespace DesignPatterns\TemplateMethod\example2;
/**
 * Class Facebook
 * @package DesignPatterns\TemplateMethod\example2
 */
class Facebook extends SocialNetwork
{

    public function logIn(string $userName, string $password): bool
    {
        echo "\n正在检查用户的凭据...\n";
        echo "名称: " . $this->username . "\n";
        echo "密码: " . str_repeat("*", strlen($this->password)) . "\n";

        $this->simulateNetworkLatency();

        echo "\n\nFacebook: '" . $this->username . "' 登录成功\n";

        return true;
    }

    public function sendData(string $message): bool
    {
        echo "Facebook: '" . $this->username . "'发布了'".$message."'\n";

        return true;
    }

    public function logOut(): void
    {
        echo "Facebook: '" . $this->username . "' 退出登录.\n";
    }
}

ZhiHu.php

<?php
namespace DesignPatterns\TemplateMethod\example2;
/**
 * Class Facebook
 * @package DesignPatterns\TemplateMethod\example2
 */
class ZhiHu extends SocialNetwork
{

    public function logIn(string $userName, string $password): bool
    {
        echo "\n正在检查用户的凭据...\n";
        echo "名称: " . $this->username . "\n";
        echo "密码: " . str_repeat("*", strlen($this->password)) . "\n";

        $this->simulateNetworkLatency();

        echo "\n\nzhihu: '" . $this->username . "' 登录成功\n";

        return true;
    }

    public function sendData(string $message): bool
    {
        echo "zhihu: '" . $this->username . "'发布了'".$message."'\n";

        return true;
    }

    public function logOut(): void
    {
        echo "zhihu: '" . $this->username . "' 退出登录.\n";
    }
}

Client.php

<?php
namespace DesignPatterns\TemplateMethod\example2;
require dirname(__DIR__).'/../vendor/autoload.php';
/**
 * Class Client
 * @package DesignPatterns\TemplateMethod\example2
 */
class Client
{
    public function run()
    {
        echo "Username: \n";
        $username = readline();
        echo "Password: \n";
        $password = readline();
        echo "Message: \n";
        $message = readline();

        echo "\nChoose the social network to post the message:\n" .
            "1 - Facebook\n" .
            "2 - zhihu\n";
        $choice = readline();

    // Now, let's create a proper social network object and send the message.
        if ($choice == 1) {
            $network = new Facebook($username, $password);
        } elseif ($choice == 2) {
            $network = new ZhiHu($username, $password);
        } else {
            die("Sorry, I'm not sure what you mean by that.\n");
        }
        $network->post($message);
    }
}
$worker = new  Client();
$worker->run();

运行 Client.php

总结

模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。这里的“算法”,我们可以理解为广义上的“业务逻辑”,并不特指数据结构和算法中的“算法”。这里的算法骨架就是“模板”,包含算法骨架的方法就是“模板方法”,这也是模板方法模式名字的由来。
在模板模式经典的实现中,模板方法定义为 final,可以避免被子类重写。需要子类重写的方法定义为 abstract,可以强迫子类去实现。不过,在实际项目开发中,模板模式的实现比较灵活,以上两点都不是必须的。

参考资料:《大话设计模式》,《深入设计模式-模板方法模式》

github示例:https://github.com/yangpanyao/design-patterns/tree/master/TemplateMethod

Last modification:July 2nd, 2020 at 07:10 pm