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()
    {
        $this->baseOperation1();
        $this->requiredOperations1();
        $this->baseOperation2();
        $this->hook1();
        $this->requiredOperation2();
        $this->baseOperation3();
        $this->hook2();
    }

    /**
     * These operations already have implementations.
     */
    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");
    }

    /**
     * These operations have to be implemented in subclasses.
     */
    protected abstract function requiredOperations1();

    protected abstract function requiredOperation2();

    /**
     * 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() { }

    protected function hook2() { }
}

/**
 * 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()
    {
        print("ConcreteClass1 says: Implemented Operation1\n");
    }

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

/**
 * Usually, concrete classes override only a fraction of base class' operations.
 */
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");
    }
}

/**
 * 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();
    // ...
}

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;

/**
 * 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($username, $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.
     */
    public abstract function logIn(string $userName, string $password): bool;

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

    public abstract function logOut();
}

/**
 * This Concrete Class implements the Facebook API (all right, it pretends to).
 */
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");
    }
}

/**
 * This Concrete Class implements the Twitter API.
 */
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");
    }
}

/**
 * A little helper function that makes waiting times feel real.
 */
function simulateNetworkLatency()
{
    $i = 0;
    while ($i < 5) {
        print(".");
        sleep(1);
        $i++;
    }
}

/**
 * The client code.
 */
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();

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