Внутренний против внешнего завода - PullRequest
5 голосов
/ 15 апреля 2011

Я размышляю об одном из двух разных способов реализации фабричного шаблона в PHP. Я не знаю, имеют ли эти варианты собственные имена, поэтому сейчас я назову их внутренней фабрикой и внешней фабрикой.

Внутренняя фабрика: фабричный метод реализован в самом классе как статический публичный метод

<?php
class Foo
{
    protected
        $loadedProps = false;

    public static factory ($id)
    {
        $class = get_called_class ();
        $item = new $class ($id);
        if ($item -> loadedProps ())
        {
            return ($item);
        }
    }

    public function loadedProps ()
    {
        return ($this -> loadedProps);
    }

    protected function loadPropsFromDB ($id)
    {
        // Some SQL logic goes here
    }

    protected function __construct ($id)
    {
        $this -> loadedProps = $this -> loadPropsFromDB ($id);
    }
}
?>

Внешняя фабрика: фабрика и элементы, которые она инициализирует, реализованы как отдельные объекты

<?php

class Foo
{
    protected
        $loadedProps = false;

    public function loadedProps ()
    {
        return ($this -> loadedProps);
    }

    protected function loadPropsFromDB ($id)
    {
        // Some SQL logic goes here
    }

    public function __construct ($id)
    {
        $this -> loadedProps = $this -> loadPropsFromDB ($id);
    }
}

abstract class FooFactory 
{
    public static factory ($id)
    {
        $item = new Foo ($id);
        if ($item -> loadedProps ())
        {
            return ($item);
        }
    }
}
?>

Теперь мне кажется, что у каждого есть свои достоинства.

Первый позволяет скрыть конструктор от внешнего мира. Это означает, что единственный способ создать объект Foo - через фабрику. Если состояние элемента не может быть загружено из БД, то фабрика вернет NULL, что вы можете легко проверить в коде.

if ($item = Foo::factory ($id))
{
    // ...
}
else
{
    // The item failed to load.  Handle error here
}

Фабрика также может создавать объекты любого подкласса Foo без каких-либо изменений.

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

Что касается внешней фабрики, то она чище, поскольку фабрика не относится к самому классу, и мне не нужно беспокоиться о том, чтобы класс взял на себя больше ответственности, чем следовало бы. Внешняя фабрика также может лучше подходить для внедрения зависимостей.

Однако у него есть свой набор недостатков. Прежде всего, конструктор элемента, который будет построен на фабрике, должен быть общедоступным, поскольку в PHP нет концепции пакетов и нет уровня защиты «пакета» для членов класса. Это означает, что ничто не мешает программисту просто выполнять новый Foo () и обходить фабрику (хотя это может упростить модульное тестирование).

Другая проблема заключается в том, что FooFactory может создавать только объекты Foo, но не любые его подклассы. Эту проблему можно обойти, добавив другой параметр в FooFactory для указания имени класса, но тогда фабрика должна будет выполнить внутренние проверки того, что указанный объектный класс на самом деле является потомком Foo.

Итак, каковы относительные достоинства двух подходов и что бы вы порекомендовали?

Кроме того, если у них больше имен собственных, чем у внутренней или внешней фабрики, я бы хотел их знать.

1 Ответ

7 голосов
/ 15 апреля 2011

На самом деле, фабрики - это созданные шаблоны проектирования, поэтому вы можете прочитать об их назначении в книге GoF или на Создание источника:

  • Абстрактная фабрика - Создает экземпляр нескольких семейств классов
  • Builder - отделяет конструкцию объекта от его представления
  • Метод фабрики - Создает экземпляр нескольких производных классов
  • Пул объектов - Избегайте дорогостоящего приобретения и высвобождения ресурсов за счет переработки объектов, которые больше не используются
  • Прототип - Полностью инициализированный экземпляр для копирования или клонирования

В этих шаблонах есть некоторое совпадение, особенно между Factory Method, Abstract Factory и Builder, и различие становится еще более размытым, когда вы создаете не семейства объектов, а только один тип объекта. Так что да, для простоты, давайте предположим, что внутренние и внешние фабрики являются правильными терминами.

Лично я всегда предпочел бы Внешнюю фабрику вместо Внутренней фабрики по той причине, которую вы уже указали: я могу использовать Внедрение зависимостей и могу разделять обязанности . Так как статические методы являются смертью для тестируемости и могут считаться вредными из-за вводимой ими связи, я бы вместо этого сделал Factory реальным объектом и использовал бы нестатические методы.

Два упомянутых вами недостатка вовсе не являются недостатками.

Я не могу придумать причину, по которой я хотел бы запретить разработчику создавать экземпляры объектов, которые создает Factory. Фактически, когда Unit-Testing я захочу создать этот объект самостоятельно и заменить все зависимости на Mocks and Stubs . Я также не верю, что разработчики должны слишком присматривать за детьми . С учетом природы сценариев PHP, изменение ctor с private на public слишком просто, чтобы его эффективно предотвратить в любом случае.

Что касается другой проблемы, связанной с неспособностью Фабрики создавать другие классы, то это не так. Идея фабрики на самом деле заключается в создании различных типов семейства объектов. Даже фабричный метод явно позволяет создавать подклассы. Независимо от того, реализуете ли вы это с помощью переключателя / корпуса или с помощью различных методов на заводе, решать только вам. Также нет причин не объединять фабрики со строителями или иметь фабрики фабрики, которые, в свою очередь, инкапсулируют логику для создания объекта. Таким образом, устраняется необходимость каких-либо внутренних проверок, о которых вы упомянули (которые также могут быть встречены с печатными буквами).

Другой жизнеспособной альтернативой фабрикам будет использование контейнера для инъекций зависимостей , такого как Symfony Components DIC , и в основном управление вашими объектами через этот контейнер.

...