🎉 Ура! Після трьох років роботи, я нарешті випустив англійську версію книжки про патерни! Ось вона »

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

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

Шаблонний метод — це поведінковий патерн, який визначає кістяк алгоритму в суперкласі та змушує підкласи реалізувати конкретні кроки цього алгоритму.

Детальніше про Шаблонний метод

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

Складність:

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

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

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

Цей приклад показує структуру патерну Шаблонний метод, а саме — з яких класів він складається, які ролі ці класи виконують і як вони взаємодіють один з одним. Після ознайомлення зі структурою, вам буде легше сприймати наступний приклад, що розглядає реальний випадок використання патерну в світі PHP.

TemplateMethodStructural.php: Приклад структури патерну

<?php

namespace RefactoringGuru\TemplateMethod\Structural;

/**
 * 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

Приклад: Приклад з життя

TemplateMethodRealWorld.php: Приклад з життя

<?php

namespace RefactoringGuru\TemplateMethod\RealWorld;

/**
 * Template Method Design Pattern
 *
 * Intent: Define the skeleton of an algorithm in operation, deferring
 * implementation of some steps to subclasses. Template Method lets subclasses
 * redefine specific steps of an algorithm without changing the algorithm's
 * structure.
 *
 * Example: In this example, the Template Method defines a skeleton of the
 * algorithm of message posting to social networks. Each subclass represents a
 * separate social network and implements all the steps differently, but reuses
 * the base algorithm.
 */

/**
 * 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.