 
                Template Method を PHP で
Template Method は、 振る舞いに関するデザインパターンの一つで、 アルゴリズムの骨組みを基底クラスで定義し、 サブクラスではアルゴリズムの全体的な構造は残したまま、 ステップを上書きします。
複雑度:
人気度:
使用例: Template Method パターンは、 PHP のフレームワークではよく見られます。 このパターンは、 クラス継承を利用して、 フレームワークのデフォルトの振る舞いの拡張を簡素化します。
見つけ方: 基底クラスにあるメソッドの一つが、 他の抽象または空のメソッドをたくさん呼んでいる場合、 Template Method を識別できます。
概念的な例
この例は、 Template Method デザインパターンの構造を説明するためのものです。 以下の質問に答えることを目的としています:
- どういうクラスからできているか?
- それぞれのクラスの役割は?
- パターンの要素同士はどう関係しているのか?
ここでパターンの構造を学んだ後だと、 これに続く、 現実世界の PHP でのユースケースが理解しやすくなります。
index.php: 概念的な例
<?php
namespace RefactoringGuru\TemplateMethod\Conceptual;
/**
 * The Abstract Class defines a template method that contains a skeleton of some
 * algorithm, composed of calls to (usually) abstract primitive operations.
 *
 * Concrete subclasses should implement these operations, but leave the template
 * method itself intact.
 */
abstract class AbstractClass
{
    /**
     * The template method defines the skeleton of an algorithm.
     */
    final public function templateMethod(): void
    {
        $this->baseOperation1();
        $this->requiredOperations1();
        $this->baseOperation2();
        $this->hook1();
        $this->requiredOperation2();
        $this->baseOperation3();
        $this->hook2();
    }
    /**
     * These operations already have implementations.
     */
    protected function baseOperation1(): void
    {
        echo "AbstractClass says: I am doing the bulk of the work\n";
    }
    protected function baseOperation2(): void
    {
        echo "AbstractClass says: But I let subclasses override some operations\n";
    }
    protected function baseOperation3(): void
    {
        echo "AbstractClass says: But I am doing the bulk of the work anyway\n";
    }
    /**
     * These operations have to be implemented in subclasses.
     */
    abstract protected function requiredOperations1(): void;
    abstract protected function requiredOperation2(): void;
    /**
     * These are "hooks." Subclasses may override them, but it's not mandatory
     * since the hooks already have default (but empty) implementation. Hooks
     * provide additional extension points in some crucial places of the
     * algorithm.
     */
    protected function hook1(): void
    {
    }
    protected function hook2(): void
    {
    }
}
/**
 * Concrete classes have to implement all abstract operations of the base class.
 * They can also override some operations with a default implementation.
 */
class ConcreteClass1 extends AbstractClass
{
    protected function requiredOperations1(): void
    {
        echo "ConcreteClass1 says: Implemented Operation1\n";
    }
    protected function requiredOperation2(): void
    {
        echo "ConcreteClass1 says: Implemented Operation2\n";
    }
}
/**
 * Usually, concrete classes override only a fraction of base class' operations.
 */
class ConcreteClass2 extends AbstractClass
{
    protected function requiredOperations1(): void
    {
        echo "ConcreteClass2 says: Implemented Operation1\n";
    }
    protected function requiredOperation2(): void
    {
        echo "ConcreteClass2 says: Implemented Operation2\n";
    }
    protected function hook1(): void
    {
        echo "ConcreteClass2 says: Overridden Hook1\n";
    }
}
/**
 * The client code calls the template method to execute the algorithm. Client
 * code does not have to know the concrete class of an object it works with, as
 * long as it works with objects through the interface of their base class.
 */
function clientCode(AbstractClass $class)
{
    // ...
    $class->templateMethod();
    // ...
}
echo "Same client code can work with different subclasses:\n";
clientCode(new ConcreteClass1());
echo "\n";
echo "Same client code can work with different subclasses:\n";
clientCode(new ConcreteClass2());
Output.txt: 実行結果
Same client code can work with different subclasses:
AbstractClass says: I am doing bulk of the work
ConcreteClass1 says: Implemented Operation1
AbstractClass says: But I let subclasses to override some operations
ConcreteClass1 says: Implemented Operation2
AbstractClass says: But I am doing bulk of the work anyway
Same client code can work with different subclasses:
AbstractClass says: I am doing bulk of the work
ConcreteClass2 says: Implemented Operation1
AbstractClass says: But I let subclasses to override some operations
ConcreteClass2 says: Overridden Hook1
ConcreteClass2 says: Implemented Operation2
AbstractClass says: But I am doing bulk of the work anyway
現実的な例
この例では、 Template Method パターンで、 ソーシャル・ネットワークにメッセージを投稿するアルゴリズムの骨組みが定義されています。 各サブクラスは個々ののソーシャルネットワークを表し、 すべてのステップを異なる方法で実装しますが、 基底アルゴリズムは再利用します。
index.php: 現実的な例
<?php
namespace RefactoringGuru\TemplateMethod\RealWorld;
/**
 * The Abstract Class defines the template method and declares all its steps.
 */
abstract class SocialNetwork
{
    protected $username;
    protected $password;
    public function __construct(string $username, string $password)
    {
        $this->username = $username;
        $this->password = $password;
    }
    /**
     * The actual template method calls abstract steps in a specific order. A
     * subclass may implement all of the steps, allowing this method to actually
     * post something to a social network.
     */
    public function post(string $message): bool
    {
        // Authenticate before posting. Every network uses a different
        // authentication method.
        if ($this->logIn($this->username, $this->password)) {
            // Send the post data. All networks have different APIs.
            $result = $this->sendData($message);
            // ...
            $this->logOut();
            return $result;
        }
        return false;
    }
    /**
     * The steps are declared abstract to force the subclasses to implement them
     * all.
     */
    abstract public function logIn(string $userName, string $password): bool;
    abstract public function sendData(string $message): bool;
    abstract public function logOut(): void;
}
/**
 * This Concrete Class implements the Facebook API (all right, it pretends to).
 */
class Facebook extends SocialNetwork
{
    public function logIn(string $userName, string $password): bool
    {
        echo "\nChecking user's credentials...\n";
        echo "Name: " . $this->username . "\n";
        echo "Password: " . str_repeat("*", strlen($this->password)) . "\n";
        simulateNetworkLatency();
        echo "\n\nFacebook: '" . $this->username . "' has logged in successfully.\n";
        return true;
    }
    public function sendData(string $message): bool
    {
        echo "Facebook: '" . $this->username . "' has posted '" . $message . "'.\n";
        return true;
    }
    public function logOut(): void
    {
        echo "Facebook: '" . $this->username . "' has been logged out.\n";
    }
}
/**
 * This Concrete Class implements the Twitter API.
 */
class Twitter extends SocialNetwork
{
    public function logIn(string $userName, string $password): bool
    {
        echo "\nChecking user's credentials...\n";
        echo "Name: " . $this->username . "\n";
        echo "Password: " . str_repeat("*", strlen($this->password)) . "\n";
        simulateNetworkLatency();
        echo "\n\nTwitter: '" . $this->username . "' has logged in successfully.\n";
        return true;
    }
    public function sendData(string $message): bool
    {
        echo "Twitter: '" . $this->username . "' has posted '" . $message . "'.\n";
        return true;
    }
    public function logOut(): void
    {
        echo "Twitter: '" . $this->username . "' has been logged out.\n";
    }
}
/**
 * A little helper function that makes waiting times feel real.
 */
function simulateNetworkLatency()
{
    $i = 0;
    while ($i < 5) {
        echo ".";
        sleep(1);
        $i++;
    }
}
/**
 * The client code.
 */
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 - Twitter\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 Twitter($username, $password);
} else {
    die("Sorry, I'm not sure what you mean by that.\n");
}
$network->post($message);
Output.txt: 実行結果
Username:
> neo
Password:
> 123123
Message:
> What is the Matrix?
Choose the social network to post the message:
1 - Facebook
2 - Twitter
> 1
Checking user's credentials...
Name: neo
Password: ******
.....
Facebook: 'neo' has logged in successfully.
Facebook: 'neo' has posted 'What is the Matrix?'.
Facebook: 'neo' has been logged out.