Шаблонный метод на PHP
Шаблонный метод — это поведенческий паттерн, задающий скелет алгоритма в суперклассе и заставляющий подклассы реализовать конкретные шаги этого алгоритма.
Сложность:
Популярность:
Применимость: Шаблонные методы можно встретить во многих PHP-фреймворках. Разработчики создают такие методы, чтобы позволить клиентам легко и быстро расширять стандартный код при помощи наследования.
Признаки применения паттерна: Класс заставляет своих потомков реализовать методы-шаги, но самостоятельно реализует структуру алгоритма.
Концептуальный пример
Этот пример показывает структуру паттерна Шаблонный метод, а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.
После ознакомления со структурой, вам будет легче воспринимать второй пример, который рассматривает реальный случай использования паттерна в мире PHP.
index.php: Пример структуры паттерна
<?php namespace RefactoringGuru\TemplateMethod\Conceptual; /** * Абстрактный Класс определяет шаблонный метод, содержащий скелет некоторого * алгоритма, состоящего из вызовов (обычно) абстрактных примитивных операций. * * Конкретные подклассы должны реализовать эти операции, но оставить сам * шаблонный метод без изменений. */ abstract class AbstractClass { /** * Шаблонный метод определяет скелет алгоритма. */ final public function templateMethod(): void { $this->baseOperation1(); $this->requiredOperations1(); $this->baseOperation2(); $this->hook1(); $this->requiredOperation2(); $this->baseOperation3(); $this->hook2(); } /** * Эти операции уже имеют реализации. */ 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"; } /** * А эти операции должны быть реализованы в подклассах. */ abstract protected function requiredOperations1(): void; abstract protected function requiredOperation2(): void; /** * Это «хуки». Подклассы могут переопределять их, но это не обязательно, * поскольку у хуков уже есть стандартная (но пустая) реализация. Хуки * предоставляют дополнительные точки расширения в некоторых критических * местах алгоритма. */ protected function hook1(): void { } protected function hook2(): void { } } /** * Конкретные классы должны реализовать все абстрактные операции базового * класса. Они также могут переопределить некоторые операции с реализацией по * умолчанию. */ class ConcreteClass1 extends AbstractClass { protected function requiredOperations1(): void { echo "ConcreteClass1 says: Implemented Operation1\n"; } protected function requiredOperation2(): void { echo "ConcreteClass1 says: Implemented Operation2\n"; } } /** * Обычно конкретные классы переопределяют только часть операций базового * класса. */ 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"; } } /** * Клиентский код вызывает шаблонный метод для выполнения алгоритма. Клиентский * код не должен знать конкретный класс объекта, с которым работает, при * условии, что он работает с объектами через интерфейс их базового класса. */ 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 Пример из реальной жизни
В этом примере Шаблонный метод определяет общую схему алгоритма отправки сообщений в социальных сетях. Каждый подкласс представляет отдельную социальную сеть и реализует все шаги по-разному, но повторно использует базовый алгоритм.
index.php: Пример из реальной жизни
<?php namespace RefactoringGuru\TemplateMethod\RealWorld; /** * Абстрактный Класс определяет метод шаблона и объявляет все его шаги. */ abstract class SocialNetwork { protected $username; protected $password; public function __construct(string $username, string $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; } /** * Шаги объявлены абстрактными, чтобы заставить подклассы реализовать их * полностью. */ abstract public function logIn(string $userName, string $password): bool; abstract public function sendData(string $message): bool; abstract public function logOut(): void; } /** * Этот Конкретный Класс реализует API Facebook (ладно, он пытается). */ 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"; } } /** * Этот Конкретный Класс реализует API Twitter. */ 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"; } } /** * Небольшая вспомогательная функция, которая делает время ожидания похожим на * реальность. */ function simulateNetworkLatency() { $i = 0; while ($i < 5) { echo "."; sleep(1); $i++; } } /** * Клиентский код. */ 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(); // Теперь давайте создадим правильный объект социальной сети и отправим // сообщение. 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.