PHP: Фабричный метод

Factory Method Фабричный метод Factory Method

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

Фабричный метод задаёт метод, который следует использовать вместо вызова оператора new для создания объектов-продуктов. Подклассы могут переопределить этот метод, чтобы изменять тип создаваемых продуктов.

Подробней о Фабричном методе

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

Сложность:

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

Применимость: Паттерн можно часто встретить в любом PHP-коде, где требуется гибкость при создании продуктов.

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

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

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

<?php

namespace RefactoringGuru\FactoryMethod\Structural;

/**
 * Класс Создатель объявляет фабричный метод, который должен возвращать объект
 * класса Продукт. Подклассы Создателя обычно предоставляют реализацию этого
 * метода.
 */
abstract class Creator
{
    /**
     * Обратите внимание, что Создатель может также обеспечить реализацию
     * фабричного метода по умолчанию.
     */
    public abstract function factoryMethod(): Product;

    /**
     * Также заметьте, что, несмотря на название,  основная обязанность
     * Создателя не заключается в создании продуктов.  Обычно он содержит
     * некоторую базовую бизнес-логику, которая основана  на объектах Продуктов,
     * возвращаемых фабричным методом.  Подклассы могут косвенно изменять эту
     * бизнес-логику, переопределяя фабричный метод и возвращая из него другой
     * тип продукта.
     */
    public function someOperation(): string
    {
        // Call the factory method to create a Product object.
        $product = $this->factoryMethod();
        // Now, use the product.
        $result = "Creator: The same creator's code has just worked with ".
            $product->operation();

        return $result;
    }
}

/**
 * Конкретные Создатели переопределяют фабричный метод для того, чтобы изменить
 * тип результирующего продукта.
 */
class ConcreteCreator1 extends Creator
{
    /**
     * Обратите внимание, что сигнатура метода по-прежнему использует тип
     * абстрактного продукта, хотя  фактически из метода возвращается конкретный
     * продукт. Таким образом, Создатель может оставаться независимым от
     * конкретных классов продуктов.
     */
    public function factoryMethod(): Product
    {
        return new ConcreteProduct1();
    }
}

class ConcreteCreator2 extends Creator
{
    public function factoryMethod(): Product
    {
        return new ConcreteProduct2();
    }
}

/**
 * Интерфейс Продукта объявляет операции, которые должны выполнять все
 * конкретные продукты.
 */
interface Product
{
    public function operation(): string;
}

/**
 * Конкретные Продукты предоставляют различные реализации интерфейса Продукта.
 */
class ConcreteProduct1 implements Product
{
    public function operation(): string
    {
        return "{Result of the ConcreteProduct1}";
    }
}

class ConcreteProduct2 implements Product
{
    public function operation(): string
    {
        return "{Result of the ConcreteProduct2}";
    }
}

/**
 * Клиентский код работает с экземпляром конкретного создателя, хотя и через его
 * базовый интерфейс. Пока клиент продолжает работать с создателем через базовый
 * интерфейс, вы можете передать ему любой подкласс создателя.
 */
function clientCode(Creator $creator)
{
    // ...
    print("Client: I'm not aware of the creator's class, but it still works.\n"
        .$creator->someOperation());
    // ...
}

/**
 * Приложение выбирает тип создателя в зависимости от конфигурации или среды.
 */
print("App: Launched with the ConcreteCreator1.\n");
clientCode(new ConcreteCreator1());
print("\n\n");

print("App: Launched with the ConcreteCreator2.\n");
clientCode(new ConcreteCreator2());

Output.txt: Результат выполнения

App: Launched with the ConcreteCreator1.
Client: I'm not aware of the creator's class, but it still works.
Creator: The same creator's code has just worked with {Result of the ConcreteProduct1}

App: Launched with the ConcreteCreator2.
Client: I'm not aware of the creator's class, but it still works.
Creator: The same creator's code has just worked with {Result of the ConcreteProduct2}

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

В этом примере паттерн Фабричный Метод предоставляет интерфейс для создания коннекторов к социальным сетям, которые могут быть использованы для входа в сеть, создания сообщений и, возможно, выполнения других действий, – и всё это без привязки клиентского кода к определённым классам конкретной социальной сети.

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

<?php

namespace RefactoringGuru\FactoryMethod\RealWorld;

/**
 * Паттерн Фабричный Метод
 *
 * Назначение: Определяет интерфейс для создания объекта, но позволяет
 * подклассам решать, какого класса создавать экземпляр. Фабричный Метод
 * позволяет классу делегировать создание экземпляра подклассам.
 *
 * Пример: В этом примере паттерн Фабричный Метод предоставляет интерфейс для
 * создания коннекторов к социальным сетям, которые могут быть использованы для
 * входа в сеть, создания сообщений и, возможно, выполнения других действий, – и
 * всё это без привязки клиентского кода к определённым классам конкретной
 * социальной сети.
 */

/**
 * Создатель объявляет фабричный метод, который может быть использован вместо
 * прямых вызовов конструктора продуктов, например:
 *
 * - До: $p = new FacebookConnector()
 * - После: $p = $this->getSocialNetwork()
 *
 * Это позволяет подклассам SocialNetworkPoster изменять тип создаваемого
 * продукта.
 */
abstract class SocialNetworkPoster
{
    /**
     * Фактический фабричный метод. Обратите внимание, что он возвращает
     * абстрактный коннектор. Это позволяет подклассам возвращать любые
     * конкретные коннекторы без нарушения контракта суперкласса.
     */
    public abstract function getSocialNetwork(): SocialNetworkConnector;

    /**
     * Когда фабричный метод используется внутри бизнес-логики Создателя,
     * подклассы могут изменять логику косвенно, возвращая из фабричного метода
     * различные типы коннекторов.
     */
    public function post($content)
    {
        // Вызываем фабричный метод для создания объекта Продукта...
        $network = $this->getSocialNetwork();
        // ...then use it as you will.
        //
        // ...а затем используем его по своему усмотрению.
        $network->logIn();
        $network->createPost($content);
        $network->logout();
    }
}

/**
 * Этот Конкретный Создатель поддерживает Facebook. Помните, что этот класс
 * также наследует метод post от родительского класса. Конкретные Создатели —
 * это классы, которые фактически использует Клиент.
 */
class FacebookPoster extends SocialNetworkPoster
{
    private $login, $password;

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

    public function getSocialNetwork(): SocialNetworkConnector
    {
        return new FacebookConnector($this->login, $this->password);
    }
}

/**
 * Этот Конкретный Создатель поддерживает LinkedIn.
 */
class LinkedInPoster extends SocialNetworkPoster
{
    private $email, $password;

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

    public function getSocialNetwork(): SocialNetworkConnector
    {
        return new LinkedInConnector($this->email, $this->password);
    }
}

/**
 * Интерфейс Продукта объявляет поведения различных типов продуктов.
 */
interface SocialNetworkConnector
{
    public function logIn();

    public function logOut();

    public function createPost($content);
}

/**
 * Этот Конкретный Продукт реализует API Facebook.
 */
class FacebookConnector implements SocialNetworkConnector
{
    private $login, $password;

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

    public function logIn()
    {
        print("Send HTTP API request to log in user $this->login with " .
            "password $this->password\n");
    }

    public function logOut()
    {
        print("Send HTTP API request to log out user $this->login\n");
    }

    public function createPost($content)
    {
        print("Send HTTP API requests to create a post in Facebook timeline.\n");
    }
}

/**
 * А этот Конкретный Продукт реализует API LinkedIn.
 */
class LinkedInConnector implements SocialNetworkConnector
{
    private $email, $password;

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

    public function logIn()
    {
        print("Send HTTP API request to log in user $this->email with " .
            "password $this->password\n");
    }

    public function logOut()
    {
        print("Send HTTP API request to log out user $this->email\n");
    }

    public function createPost($content)
    {
        print("Send HTTP API requests to create a post in LinkedIn timeline.\n");
    }
}

/**
 * Клиентский код может работать с любым подклассом SocialNetworkPoster, так как
 * он не зависит от конкретных классов.
 */
function clientCode(SocialNetworkPoster $creator)
{
    // ...
    $creator->post("Hello world!");
    $creator->post("I had a large hamburger this morning!");
    // ...
}

/**
 * На этапе инициализации приложение может выбрать, с какой социальной сетью оно
 * хочет работать, создать объект соответствующего подкласса и передать его
 * клиентскому коду.
 */
print("Testing ConcreteCreator1:\n");
clientCode(new FacebookPoster("john_smith", "******"));
print("\n\n");

print("Testing ConcreteCreator2:\n");
clientCode(new LinkedInPoster("john_smith@example.com", "******"));

Output.txt: Результат выполнения

Testing ConcreteCreator1:
Send HTTP API request to log in user john_smith with password ******
Send HTTP API requests to create a post in Facebook timeline.
Send HTTP API request to log out user john_smith
Send HTTP API request to log in user john_smith with password ******
Send HTTP API requests to create a post in Facebook timeline.
Send HTTP API request to log out user john_smith


Testing ConcreteCreator2:
Send HTTP API request to log in user john_smith@example.com with password ******
Send HTTP API requests to create a post in LinkedIn timeline.
Send HTTP API request to log out user john_smith@example.com
Send HTTP API request to log in user john_smith@example.com with password ******
Send HTTP API requests to create a post in LinkedIn timeline.
Send HTTP API request to log out user john_smith@example.com