Концептуальный пример
Этот пример показывает структуру паттерна Шаблонный метод , а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.
После ознакомления со структурой, вам будет легче воспринимать второй пример, который рассматривает реальный случай использования паттерна в мире 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.