Назначение
Паттерн решает задачу определения некоторого интерфейса по созданию класса, конкретную реализацию которого определяют дочерние классы, реализующие данный интерфейс. В отличие от абстрактной фабрики, фабричный метод оперирует созданием одного единственного объекта, а не семейства.
Данный паттерн имеет несколько версий реализации, мы рассмотрим реализацию через статический метод в базовом классе. В такой реализации в статический метод поступают некоторые аргументы, на основе которых принимается решение о создании того или иного наследника базового класса.
Преимущества:
- Избавляет от необходимости лишнего связывания клиентского кода с дочерними классами, т.е. место, где создаются наследники явно определено в статическом методе базового класса, а не скрыто где-то у клиента. Клиент, как и в случае с абстрактной фабрикой не завязан на конкретные реализации, а оперирует абстракцией.
Диаграмма
Пример
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
PARAMETERS: p_type TYPE i. *&---------------------------------------------------------------------* *& Определение классов *&---------------------------------------------------------------------* CLASS lcl_base_writer DEFINITION. PUBLIC SECTION. TYPES: ty_report_type TYPE i. CONSTANTS: gc_report_pdf TYPE ty_report_type VALUE 1, gc_report_write TYPE ty_report_type VALUE 2, gc_report_alv TYPE ty_report_type VALUE 3. CLASS-METHODS: get_writer importing iv_type type ty_report_type default gc_report_write returning value(ro_writer) TYPE REF TO lcl_base_writer. METHODS: write_data. ENDCLASS. "lcl_base_writer DEFINITION CLASS lcl_write_writer DEFINITION INHERITING FROM lcl_base_writer. PUBLIC SECTION. METHODS: write_data REDEFINITION. ENDCLASS. "lcl_write_writer DEFINITION CLASS lcl_alv_writer DEFINITION INHERITING FROM lcl_base_writer. PUBLIC SECTION. METHODS: write_data REDEFINITION. ENDCLASS. "lcl_alv_writer DEFINITION CLASS lcl_pdf_writer DEFINITION INHERITING FROM lcl_base_writer. PUBLIC SECTION. METHODS: write_data REDEFINITION. ENDCLASS. "lcl_pdf_writer DEFINITION *&---------------------------------------------------------------------* *& Реализация классов *&---------------------------------------------------------------------* CLASS lcl_base_writer IMPLEMENTATION. METHOD get_writer. CASE iv_type. WHEN gc_report_pdf. CREATE OBJECT ro_writer TYPE lcl_pdf_writer. WHEN gc_report_write. CREATE OBJECT ro_writer TYPE lcl_write_writer. WHEN gc_report_alv. CREATE OBJECT ro_writer TYPE lcl_alv_writer. WHEN OTHERS. " RAISE EXCEPTION. ENDCASE. ENDMETHOD. "get_writer METHOD write_data. "RAISE EXCEPTION... WRITE: / 'Use factory method!'. ENDMETHOD. ENDCLASS. "lcl_base_writer IMPLEMENTATION CLASS lcl_pdf_writer IMPLEMENTATION. METHOD write_data. WRITE: / 'Write with pdf'. ENDMETHOD. "write_data ENDCLASS. "lcl_pdf_writer IMPLEMENTATION CLASS lcl_write_writer IMPLEMENTATION. METHOD write_data. WRITE: / 'Write with write'. ENDMETHOD. "write_data ENDCLASS. "lcl_write_writer IMPLEMENTATION CLASS lcl_alv_writer IMPLEMENTATION. METHOD write_data. WRITE: / 'Write with alv'. ENDMETHOD. "write_data ENDCLASS. "lcl_alv_writer IMPLEMENTATION *&---------------------------------------------------------------------* *& Реализация клиента *&---------------------------------------------------------------------* START-OF-SELECTION. DATA: lo_writer TYPE REF TO lcl_base_writer. lo_writer = lcl_base_writer=>get_writer( p_type ). lo_writer->write_data( ). |
Как видно у базового класса определен фабричный метод get_writer, который в зависимости от передаваемого в него типа будет создавать того или иного наследника. Клиент работает с абстракцией, и фабричный метод скрывает от него иерархию наследования.
В данном случае сделан один общий метод для получения инстанции, однако зачастую бывает лучше создать отдельные методы, чтобы не перегружать общий фабричный метод ненужными аргументами, которые могут потребоваться для создания одного наследника и не потребоваться для создания другого.
«Бывает лучше создать отдельные методы, чтобы не перегружать общий фабричный метод ненужными аргументами» — это уже получается какое-то семейство фабричных методов (статиков) в рамках одного родителя. Не нужно ли в подобных ситуациях поставить вопрос — а правильно ли вообще организовано наследование?
Думаю что не всегда.
Предположим есть некоторый абстрактный LogSaver, у которого есть методы для записи журнала куда-либо. И два наследника, один для записи в файл, другой в таблицу.
Клиент работает с абстрактным интерфейсом.
Для создания первого необходимо указывать имя файла, для второго ничего не нужно.
Загромождать интерфейс статического метода думаю не имеет особого смысла. Проще сделать два: fromFile и fromTable, при этом от клиента иерархия наследования так же будет скрыта.
А что не так в этом случае с наследованием?
Так прелесть фабричного метода как раз в том, что этот метод — один, разве нет?
Если же у вас один метод будет создавать 4 вида «логгеров», а второй — еще 3, которые похожи по входных атрибутам, то может быть стоит создать два наследника каждый со своим фабричным методом, а вот от них уже наследовать варианты реализации?
Хотя, возможно, и тот и тот вариант имеет место быть… Просто хотелось бы увидеть реальный случай (без предположений), когда имеет смысл делать несколько фабричных методов. У вас такой имеется?
«может быть стоит создать два наследника каждый со своим фабричным методом, а вот от них уже наследовать варианты реализации?»
К сожалению в таком случае, будет расти количество зависимостей у клиента, т.к. придётся вызывать метод либо класса А, либо класса Б (наследники).
В любом случае можно воспользоваться альтернативным решением, обернуть данные необходимые конкретным наследникам в объект и уже в каждом из наследников получать те данные, что ему нужны (lo_data_object->get_parameter( id = ‘parameter_for_class_a’ ).
Как по мне вариант с несколькими методами более прозрачен и прост в понимании, а простота это именно то к чему мы должны стремиться.
Если же вектор изменений у нас будет в увеличении количества вариантов создания объектов, создавать каждый раз новый метод не будет подходящим решением. Так что тут все зависит от контекста 🙂
Пример нескольких фабричных методов которые используются в качестве именованных конструкторов (из .Net):
Timespan.FromSecond, Timespan.FromMilliseconds…
эм, а разве он в результате не возвращает один и тот же объект класса , а не наследников? К сожалению, проверить возможности нет, ковыряюсь в справке:
public static TimeSpan FromSeconds( double value )
public static TimeSpan FromMilliseconds( double value )
Или это родительский класс, а по факту возвращаются экземпляры дочерних классов?
Возвращает действительно один и тот же. Пример конечно не совсем корректный. Но паттерн может применяться как в указанном примере в виде именованного конструктора, а может так же скрывать наследников, как это сделано например в классе WebRequest.Create(..) который скрывает иерархию наследования. Но ведь никто не запрещает делать и то и другое одновременно (скрывать иерархию в именованных конструкторах) 🙂
Согласен.
Поговорил с коллегой, кажется нашли неплохой пример в стандарте — CL_ABAP_TYPEDESCR. Есть несколько фабричных методов (DESCRIBE_BY_DATA, _NAME, …), которые возвращают дочерние классы.
Супер, спасибо за пример.
Согласен, нужно всегда стремиться к упрощению понимания и использования архитектуры.
Да, и еще, чтобы не делать Exception внутри метода Write родительского класса, достаточно сделать его абстрактным. Собственно, можно еще закрыть создание экземпляра род. класса, тоже объявив его абстрактным.
Недавно сделал реализации двух видов логов: для SLG1 и для хранящегося только в оперативной памяти. Собственно, класс родительский с общим набором абстрактных методов и фабричным методом + добавил интерфейс в дочерний класс SLG, чтобы была возможность сохранить его. Если интересно, могу куда-нибудь скинуть реализацию.
Согласен
У меня (да и много где еще) подобный функционал реализован в 1 классе, а возможность сохранения в памяти или БД реализована через вызов соответствующих методов и передачу необязательных параметров с ИД журнала.
Ребят, а почему строке 35 снова объявление параметра?
Ошибочка, убрал