PHP: Шаблонный метод

Template Method Шаблонный метод Template Method

Шаблонный метод — это поведенческий паттерн, задающий скелет алгоритма в суперклассе и заставляющий подклассы реализовать конкретные шаги этого алгоритма.

Подробней о Шаблонном методе

Особенности паттерна на PHP

Сложность:

Популярность:

Применимость: Шаблонные методы можно встретить во многих PHP-фреймворках. Разработчики создают такие методы, чтобы позволить клиентам легко и быстро расширять стандартный код при помощи наследования.

Признаки применения паттерна: Класс заставляет своих потомков реализовать методы-шаги, но самостоятельно реализует структуру алгоритма.

Пример: Структура паттерна

Этот пример показывает структуру паттерна Шаблонный метод, а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом. После ознакомления со структурой, вам будет легче воспринимать второй пример, который рассматривает реальный случай использования паттерна в мире PHP.

TemplateMethodStructural.php: Пример структуры паттерна

<?php

namespace RefactoringGuru\TemplateMethod\Structural;

/**
 * Абстрактный Класс определяет шаблонный метод, содержащий скелет некоторого
 * алгоритма, состоящего из вызовов (обычно) абстрактных примитивных операций.
 *
 * Конкретные подклассы должны реализовать эти операции, но оставить сам
 * шаблонный метод без изменений.
 */
abstract class AbstractClass
{
    /**
     * Шаблонный метод определяет скелет алгоритма.
     */
    final public function templateMethod()
    {
        $this->baseOperation1();
        $this->requiredOperations1();
        $this->baseOperation2();
        $this->hook1();
        $this->requiredOperation2();
        $this->baseOperation3();
        $this->hook2();
    }

    /**
     * Эти операции уже имеют реализации.
     */
    protected function baseOperation1()
    {
        print("AbstractClass says: I am doing the bulk of the work\n");
    }

    protected function baseOperation2()
    {
        print("AbstractClass says: But I let subclasses override some operations\n");
    }

    protected function baseOperation3()
    {
        print("AbstractClass says: But I am doing the bulk of the work anyway\n");
    }

    /**
     * А эти операции должны быть реализованы в подклассах.
     */
    protected abstract function requiredOperations1();

    protected abstract function requiredOperation2();

    /**
     * Это «хуки». Подклассы могут переопределять их, но это не обязательно,
     * поскольку у хуков уже есть стандартная (но пустая) реализация.  Хуки
     * предоставляют дополнительные точки расширения в некоторых критических
     * местах алгоритма.
     */
    protected function hook1() { }

    protected function hook2() { }
}

/**
 * Конкретные классы должны реализовать все абстрактные операции базового
 * класса. Они также могут переопределить некоторые операции с реализацией по
 * умолчанию.
 */
class ConcreteClass1 extends AbstractClass
{
    protected function requiredOperations1()
    {
        print("ConcreteClass1 says: Implemented Operation1\n");
    }

    protected function requiredOperation2()
    {
        print("ConcreteClass1 says: Implemented Operation2\n");
    }
}

/**
 * Обычно конкретные классы переопределяют только часть операций базового
 * класса.
 */
class ConcreteClass2 extends AbstractClass
{
    protected function requiredOperations1()
    {
        print("ConcreteClass2 says: Implemented Operation1\n");
    }

    protected function requiredOperation2()
    {
        print("ConcreteClass2 says: Implemented Operation2\n");
    }

    protected function hook1()
    {
        print("ConcreteClass2 says: Overridden Hook1\n");
    }
}

/**
 * Клиентский код вызывает шаблонный метод для выполнения алгоритма. Клиентский
 * код не должен знать конкретный класс объекта, с которым работает, при
 * условии, что он работает с объектами через интерфейс их базового класса.
 */
function clientCode(AbstractClass $class)
{
    // ...
    $class->templateMethod();
    // ...
}

print("Same client code can work with different subclasses:\n");
clientCode(new ConcreteClass1());
print("\n");

print("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

Пример: Пример из жизни

В этом примере Шаблонный метод определяет общую схему алгоритма отправки сообщений в социальных сетях. Каждый подкласс представляет отдельную социальную сеть и реализует все шаги по-разному, но повторно использует базовый алгоритм.

TemplateMethodRealWorld.php: Пример из жизни

<?php

namespace RefactoringGuru\TemplateMethod\RealWorld;

/**
 * Паттерн Шаблонный метод
 *
 * Назначение: Определяет общую схему алгоритма, перекладывая реализацию
 * некоторых шагов  на подклассы. Шаблонный метод позволяет подклассам
 * переопределять отдельные шаги алгоритма без изменения структуры алгоритма.
 *
 * Пример: В этом примере Шаблонный метод определяет общую схему алгоритма
 * отправки сообщений в социальных сетях. Каждый подкласс представляет отдельную
 * социальную сеть и реализует все шаги по-разному, но повторно использует
 * базовый алгоритм.
 */

/**
 * Абстрактный Класс определяет метод шаблона и объявляет все его шаги.
 */
abstract class SocialNetwork
{
    protected $username;

    protected $password;

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

    /**
     * Фактический метод шаблона вызывает абстрактные шаги в определённом
     * порядке. Подкласс может реализовать все шаги, позволяя этому методу
     * реально публиковать что-то в социальной сети.
     */
    public function post(string $message): bool
    {
        // Проверка подлинности перед публикацией. Каждая сеть использует свой
        // метод авторизации.
        if ($this->logIn($this->username, $this->password)) {
            // Отправляем почтовые данные. Все сети имеют разные API.
            $result = $this->sendData($message);
            // ...
            $this->logOut();

            return $result;
        }

        return false;
    }

    /**
     * Шаги объявлены абстрактными, чтобы заставить подклассы реализовать их
     * полностью.
     */
    public abstract function logIn(string $userName, string $password): bool;

    public abstract function sendData(string $message): bool;

    public abstract function logOut();
}

/**
 * Этот Конкретный Класс реализует API Facebook (ладно, он пытается).
 */
class Facebook extends SocialNetwork
{
    public function logIn(string $userName, string $password): bool
    {
        print("\nChecking user's credentials...\n");
        print("Name: ".$this->username."\n");
        print("Password: ".str_repeat("*", strlen($this->password))."\n");

        simulateNetworkLatency();

        print("\n\nFacebook: '".$this->username."' has logged in successfully.\n");

        return true;
    }

    public function sendData(string $message): bool
    {
        print("Facebook: '".$this->username."' has posted '".$message."'.\n");

        return true;
    }

    public function logOut()
    {
        print("Facebook: '".$this->username."' has been logged out.\n");
    }
}

/**
 * Этот Конкретный Класс реализует API Twitter.
 */
class Twitter extends SocialNetwork
{
    public function logIn(string $userName, string $password): bool
    {
        print("\nChecking user's credentials...\n");
        print("Name: ".$this->username."\n");
        print("Password: ".str_repeat("*", strlen($this->password))."\n");

        simulateNetworkLatency();

        print("\n\nTwitter: '".$this->username."' has logged in successfully.\n");

        return true;
    }

    public function sendData(string $message): bool
    {
        print("Twitter: '".$this->username."' has posted '".$message."'.\n");

        return true;
    }

    public function logOut()
    {
        print("Twitter: '".$this->username."' has been logged out.\n");
    }
}

/**
 * Небольшая вспомогательная функция, которая делает время ожидания похожим на
 * реальность.
 */
function simulateNetworkLatency()
{
    $i = 0;
    while ($i < 5) {
        print(".");
        sleep(1);
        $i++;
    }
}

/**
 * Клиентский код.
 */
print("Username: \n");
$username = readline();
print("Password: \n");
$password = readline();
print("Message: \n");
$message = readline();

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

// Теперь давайте создадим правильный объект социальной сети и отправим
// сообщение.
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.