Фабричный метод (Factory method)

Назначение

 

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

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

Преимущества:

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

 

Диаграмма

 

 

2-FactoryMethod

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

 

Пример

 

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

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

  • svirinstel

    «Бывает лучше создать отдельные методы, чтобы не перегружать общий фабричный метод ненужными аргументами» — это уже получается какое-то семейство фабричных методов (статиков) в рамках одного родителя. Не нужно ли в подобных ситуациях поставить вопрос — а правильно ли вообще организовано наследование?

    • Думаю что не всегда.
      Предположим есть некоторый абстрактный LogSaver, у которого есть методы для записи журнала куда-либо. И два наследника, один для записи в файл, другой в таблицу.

      Клиент работает с абстрактным интерфейсом.

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

      Загромождать интерфейс статического метода думаю не имеет особого смысла. Проще сделать два: fromFile и fromTable, при этом от клиента иерархия наследования так же будет скрыта.

      А что не так в этом случае с наследованием?

      • svirinstel

        Так прелесть фабричного метода как раз в том, что этот метод — один, разве нет?

        Если же у вас один метод будет создавать 4 вида «логгеров», а второй — еще 3, которые похожи по входных атрибутам, то может быть стоит создать два наследника каждый со своим фабричным методом, а вот от них уже наследовать варианты реализации?

        Хотя, возможно, и тот и тот вариант имеет место быть… Просто хотелось бы увидеть реальный случай (без предположений), когда имеет смысл делать несколько фабричных методов. У вас такой имеется?

        • «может быть стоит создать два наследника каждый со своим фабричным методом, а вот от них уже наследовать варианты реализации?»

          К сожалению в таком случае, будет расти количество зависимостей у клиента, т.к. придётся вызывать метод либо класса А, либо класса Б (наследники).

          В любом случае можно воспользоваться альтернативным решением, обернуть данные необходимые конкретным наследникам в объект и уже в каждом из наследников получать те данные, что ему нужны (lo_data_object->get_parameter( id = ‘parameter_for_class_a’ ).

          Как по мне вариант с несколькими методами более прозрачен и прост в понимании, а простота это именно то к чему мы должны стремиться.

          • Если же вектор изменений у нас будет в увеличении количества вариантов создания объектов, создавать каждый раз новый метод не будет подходящим решением. Так что тут все зависит от контекста 🙂

            • Пример нескольких фабричных методов которые используются в качестве именованных конструкторов (из .Net):
              Timespan.FromSecond, Timespan.FromMilliseconds…

              • svirinstel

                эм, а разве он в результате не возвращает один и тот же объект класса , а не наследников? К сожалению, проверить возможности нет, ковыряюсь в справке:
                public static TimeSpan FromSeconds( double value )
                public static TimeSpan FromMilliseconds( double value )
                Или это родительский класс, а по факту возвращаются экземпляры дочерних классов?

              • Возвращает действительно один и тот же. Пример конечно не совсем корректный. Но паттерн может применяться как в указанном примере в виде именованного конструктора, а может так же скрывать наследников, как это сделано например в классе WebRequest.Create(..) который скрывает иерархию наследования. Но ведь никто не запрещает делать и то и другое одновременно (скрывать иерархию в именованных конструкторах) 🙂

              • svirinstel

                Согласен.
                Поговорил с коллегой, кажется нашли неплохой пример в стандарте — CL_ABAP_TYPEDESCR. Есть несколько фабричных методов (DESCRIBE_BY_DATA, _NAME, …), которые возвращают дочерние классы.

              • Супер, спасибо за пример.

          • svirinstel

            Согласен, нужно всегда стремиться к упрощению понимания и использования архитектуры.

  • svirinstel

    Да, и еще, чтобы не делать Exception внутри метода Write родительского класса, достаточно сделать его абстрактным. Собственно, можно еще закрыть создание экземпляра род. класса, тоже объявив его абстрактным.

    Недавно сделал реализации двух видов логов: для SLG1 и для хранящегося только в оперативной памяти. Собственно, класс родительский с общим набором абстрактных методов и фабричным методом + добавил интерфейс в дочерний класс SLG, чтобы была возможность сохранить его. Если интересно, могу куда-нибудь скинуть реализацию.

    • Согласен

    • У меня (да и много где еще) подобный функционал реализован в 1 классе, а возможность сохранения в памяти или БД реализована через вызов соответствующих методов и передачу необязательных параметров с ИД журнала.

  • Сергей

    Ребят, а почему строке 35 снова объявление параметра?