Фабричный метод (Factory Method). Пример на php

Поговорим о самом простом, на первый взгляд, паттерне. Объясню почему он очень часто недопонятый из всех порождающих. И, конечно, пример на php

Дисклеймер

Сразу хочу снять с себя всю ответственность за пояснения по тексту, я буду описывать отличия и особенности между паттернами в рамках своего понимания паттернов. Мне конечно пришлось проработать много литературы на этот счёт, но есть ненулевая вероятность, что моё понимание предмета и его истинное значение могут не совпадать. Поэтому не надо закидывать меня кАкушками, но за конструктивную критику в комментариях буду благодарен и готов вносить правки.

Полный перечень паттернов описан в отдельном посте.
Там же общие слова и мысли о необходимости паттернов в жизни программиста.

Фабричный метод — порождающий паттерн, задачей которого является определение единого интерфейс создания объектов в базовом классе. В общем случае дочерние классы обязаны описать собственную реализацию создающего метода.

Вообще, с рассматриваемым паттерном — фабричный метод, очень всё не просто. Далеко не любой метод создающий объекты можно назвать фабричным.

Вот например

class Time
{
    private $hour;

    private $minute;

    private $second;

    public function __construct(int $hour, int $minute, int $second)
    {
        $this->hour   = $hour;
        $this->minute = $minute;
        $this->second = $second;
    }

    public static function fromString(string $time)
    {
        if (!preg_match('/\d+:\d+:\d+/', $time)) {
            throw new DomainException('Fail string format. Must by H:i:s');
        }

        $explode = explode(':', $time);

        return new self($explode[0], $explode[1], $explode[2]);
    }
}

Метод fromString не может носить гордое название паттерна «Фабричный метод». Поскольку «Фабричный метод» предполагает иерархию объектов. Т.е. должен быть некий родительский класс с порождающим методом, возможно даже абстрактным порождающим методом. А все дочерние классы, должны определять свою реализацию этого метода. И вот тогда мы имеем дело с реализацией рассматриваемого паттерна.

Правильный пример

Есть объект транзакций, который должен уметь работать с несколькими платёжными системами.

abstract class Transaction
{
    abstract public function getBilling(): BillingService;

    public function setPayment(int $hour, int $minute, int $second)
    {
        $billingService = $this->getBilling();
        
        // создание транзакции в БД
        $id = $this->createInBd();

        // отправка запроса в платежный сервис
        $res = $billingService->sendPayments();
        
        // при успешности установим локальный статус успешности оплаты
        if( $res ) {
            $this->setSuccess();
        }
    }
}

Метод setPayment внутри себя использует BillingService, который инстанцируется специальным методом, тем самым «Фабричным методом».

Теперь предположим, что у нас есть 2 платёжных сервиса: Billing1 и Billing2. Они работают внутри себя по-разному, но должны реализовывать единый интерфейс, например

interface BillingService
{
    public function sendPayments();
    public function getStatus();
}

Не будем влезать в конкретику реализации. Предположим, что первая платёжка в конструктор принимает 1 параметр — токен, а вторая 2 параметра, имя аккаунта и токен. А теперь пропишем как бы могли выглядеть реализации класса транзакции для этих платёжек.

class Billing1Transaction extends Transaction
{
    private $token;

    public function __construct(sting $token)
    {
        $this->token = $token;
    }

    public function getBilling(): BillingService
    {
        return new Billing1($this->token)
    }
}

class Billing2Transaction extends Transaction
{
    private $account;
    private $token;

    public function __construct(sting $account, sting $token)
    {
        $this->account = $account;
        $this->token = $token;
    }

    public function getBilling(): BillingService
    {
        return new Billing2($this->account, $this->token)
    }
}

Таким образом вся логика по работе с транзакцией может быть описана в исходном абстрактном классе. А его потомки только содержат определение конкретных реализаций платёжных систем.

Но вот сейчас мне пришла мысль, а что нам может помешать, имея общий интерфейс самих платёжек прокидывать зависимость через конструктор Транзакции? Но если у порождаемых объектов нельзя выделить единый интерфейс, и вообще всё логика исходного класса транзакций меняется в зависимости от платёжки, то паттерн имеем место быть.

Итого

В чистом виде, мне с этим паттерном встречаться не приходилось. Обычно мы делали что-то очень похожее на фабричный метод, но не в его каноническом смысле.

Понравилась статья? Поделиться с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!:

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.