Фабрика должна только создавать, а не сохранять

В недавнем разговоре на Reddit, мы подняли тему касательно паттерна проектирования «Фабрика». Я придерживался тогда (и сейчас) мнения, что фабрика всегда и только возвращает новый экземпляр. Если у вас есть фабрика, которая возвращает что-то большее, чем новый экземпляр, то это не просто фабрика. В случае фабричных методов, это фабрика + метод доступа (аксессор); в случае фабрики, это фабрика + реестр (registry).

Фабрика (будь то фабричный метод или объект фабрики) является одним из способов, чтобы отделить создание объекта от использования объекта.

Скажем, нам нужно создать объект, чтобы сделать некоторую работу в методе класса:

class Example
{
    public function __construct($db)
    {
        $this->db = $db;
    }

    public function doSomething($itemId)
    {
        $data = $this->db->fetchOne(
            "SELECT * FROM items WHERE id = :id",
            ['id' => $itemId]
        );
        $item = new Item($data);

        // сделать ещё какую-нибудь работу с элементом,
        // после чего вернуть результат работы.
        return $results;
    }
}

Создание элемента смешивается с использованием элемента. Но мы хотим отделить создание от использования. Одним из способов, который позволит это сделать, является использование фабричного метода или объекта фабрики. Вот пример фабричным методом:

class Example
{
    public function __construct($db)
    {
        $this->db = $db;
    }

    public function doSomething($itemId)
    {
        $data = $this->db->fetchOne(
            "SELECT * FROM items WHERE id = ?",
            ['id' => $id]
        );
        $item = $this->item($itemId);

        // сделать ещё какую-нибудь работу с элементом,
        // после чего вернуть результат работы.
        return $results;
    }

    public function item($data) // фабричный метод
    {
        return new Item($data);
    }
}

Вот пример внедрённого объекта фабрики:

class Example
{
    public function __construct($db, $factory)
    {
        $this->db = $db;
        $this->factory = $factory;
    }

    public function doSomethingWithItem($id)
    {
        $data = $this->db->fetchOne(
            "SELECT * FROM items WHERE id = ?",
            ['id' => $id]
        );
        $item = $this->factory->item($data);

        // сделать ещё какую-нибудь работу с элементом,
        // после чего вернуть результат работы.

        return $results;
    }
}

class Factory // объект фабрики
{
    public function item($data) // фабричный метод
    {
        return new Item($data);
    }
}

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

Например, скажем, мы создаём и повторно используем объект какого-то сотрудника:

class Example
{
    public function doSomething()
    {
        $collab = $this->collab();
        // делать что-то, а затем:
        return $result;
    }

    protected function collab() // фабричный метод?
    {
        if (! $this->collab) {
            $this->collab = new Collab();
        }
        return $this->collab;
    }
}

Насколько я могу сказать, это не только фабрика. Есть три вещи, происходящие там: инициализация свойства, создание объекта и предоставление доступа к свойству. Чтобы разделить эти процессы, нужно использовать два метода:

class Example
{
    public function doSomething()
    {
        $collab = $this->getCollab();
        // делать что-то, затем:
        return $result;
    }

    protected function getCollab() // инициализация + предоставление доступа
    {
        if (! $this->collab) {
            $this->collab = $this->newCollab();
        }
        return $this->collab;
    }

    protected function newCollab() // фабрика
    {
        return new Collab();
    }
}

Теперь процесс создания объекта представлен методом newCollab(), а инициализация и предоставление доступа к свойству представлены методом getCollab(). Действительно, разделение фабричного метода в такой ситуации является точной рекомендацией книги Банды Четырёх «Шаблоны проектирования» на странице 113; это книга, которая определяет термин «фабричный метод» в первую очередь, поэтому, кажется, что она авторитетна в данном вопросе.

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

class WhatIsThatThing
{
    public function getService()
    {
        if (! $this->service) {
            $this->service = $this->newService();
        }
        return $this->service;
    }

    public function newService()
    {
        return new Service();
    }
}

Этот код и создаёт, и сохраняет созданные объекты. Значит, он выполняет больше операций, чем просто фабрика; это больше похоже на контейнер (container at this point).

Наконец, что касается имён методов, то, кажется, что лучше указать, какой ожидать возвращаемый результат. Я по умолчанию пишу newInstance() в фабриках, которые имеют дело только с одним классом, и new<Type>(), когда фабрика создаёт различные классы. Использование get<Type>() указывает, что возвращается предварительно существующий экземпляр (вероятно, сохраняемый в свойстве).

Оригинал: http://paul-m-jones.com/archives/6161

Комментарии

  1. Буранчик пишет:

    Высказывание исключительно для программирования применимо или его можно употреблять и относительно фабрик, производящих техническое оборудование?

    • Includen пишет:

      Да, мы пришли к мнению, что высказывание применимо и соблюдается для реальных фабрик.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *