Паттерн фабричный метод тестовые вопросы. Порождающие паттерны. Классическая реализация паттерна Factory Method

1. Название : Factory Method

2. Задачи:

    Система должна оставаться расширяемой путем добавления объектов новых типов. Непосредственное использование выражения new является нежелательным, так как в этом случае код создания объектов с указанием конкретных типов может получиться разбросанным по всему приложению. Тогда такие операции как добавление в систему объектов новых типов или замена объектов одного типа на другой будут затруднительными (подробнее в разделе Порождающие паттерны ). Паттерн Factory Method позволяет системе оставаться независимой как от самого процесса порождения объектов, так и от их типов.

    Заранее известно, когда нужно создавать объект, но неизвестен его тип.

3. Решение:

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

Для обеспечения относительно простого добавления в систему новых типов паттерн Factory Method локализует создание объектов конкретных типов в специальном классе-фабрике. Методы этого класса, посредством которых создаются объекты конкретных классов, называются фабричными.

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

4. UML-диаграмма классов паттерна Factory Method. Классическая реализация

Product - собственно продукт. Предназначен для определения интерфейса объектов, создаваемых фабричным методом;

ConcreteProduct (Computer) - конкретные продукты, которые участвуют в схеме, и отвечают за реализацию абстрактного класса (интерфейса) Product.

Creator - создатель, и его название говорит само за себя. Данный объект предназначен для объявления фабричного метода, возвращающего объект типа Product.

ConcreteCreator - конкретный создатель. Здесь все очевидно: конкретная реализация создателя занимается тем, что возвращает конкретный продукт. В нашем примере конкретная реализация создателя - ComputerCreator.

Создатель доверяет своим подклассам реализацию подходящего конкретного продукта. В этом и заключается суть Factory Method .

5. Пример реализации Factory Method:

Класс Product - предназначен для определения интерфейса объектов, создаваемых фабричным методом. Это как бы базовая оболочка для продуктов. Продукт имеет цену и т.д.

    abstract class Product

    public abstract decimal PurchasePrice {get ; set ;}

    public abstract decimal Price {get ; set ;}

    public abstract string Description {get ; set ;}

Создаем класс, унаследованный от класса Product, который будет инкапсулировать логику конкретного продукта:

    class Computer: Product

    private decimal _purchase_price;

    private decimal _price;

    private string _description;

    public Computer(string _description, decimal _purchase_price,

    decimal _price)

    this ._description = _description;

    this ._purchase_price = _purchase_price;

    this ._price = _price;

    public override string Description

    get { return _description; }

    set { _description = value; }

    public override decimal Price

    get { return _price; }

    set { _price = value; }

    public override decimal PurchasePrice

    get { return _purchase_price; }

    set { _purchase_price = value; }

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

    abstract class Creator

    public abstract Product FactoryMethod(string _description,

    decimal _purchase_price, decimal _price);

Создаем конкретный класс создатель конкретного продукта (унаследован от Creator). В этом классе определяется метод для конструктора класса Computer (если конструкторов несколько, то для каждого конструктора определяется свой фабричный метод):

    class ComputerCreator: Creator

    public override Product FactoryMethod(string _description,

    decimal _purchase_price, decimal _price)

    return new Computer(_description,_purchase_price,_price);

Клиентский код:

    static void Main(string args)

    ListProductList = new List

    Creator creators = new Creator;

    creators = new ComputerCreator();

    foreach (Creator cr in creators)

    if (cr is ComputerCreator)

    productList.Add(cr.FactoryMethod("Ноут бук", 600, 800));

    foreach (Product pr in productList)

    Console.WriteLine("Обьект класса {0};\n" +

    "Описание: {1};\n" +

    "Закупочная цена: {2};\n" +

    "Цена продажы: {3};\n",

    pr.GetType().Name,

  1. pr.PurchasePrice,

Результат программы:

6. Плюсы и минусы данного паттерна:

Самый очевидный недостаток Factory Method - необходимость создавать наследника Creator всегда, когда планируется получить новый тип продукта (т.е. новый ConcreteProduct). И этого, увы, не избежать. Но подобная проблема присутствует во многих порождающих шаблонах. К достоинствам же следует отнести возможность создавать объекты более универсально, не ориентируясь на конкретные классы и оперируя общим интерфейсом.

Factory Method - это паттерн создания объектов (creational pattern). Данный шаблон проектирования предоставляет интерфейс для создания экземпляров некоторого класса. В момент создания наследники могут определить, какой класс инстанциировать.

Иными словами, Фабрика делегирует создание объектов наследникам родительского класса. Это позволяет использовать в коде программы не специфические классы, а манипулировать абстрактными объектами на более высоком уровне.

Паттерн проектирования Factory встречается очень часто. Рассмотрим небольшой пример на Java.

Вступление: Требования к разного рода програмным продуктам постоянно растут. Отчеты по выполнению операций приложения должны формироваться в разном виде: XML, HTML, текст и т.д. Это как раз тот случай, когда удобно использовать паттерн Factory.

Решение: Класс AbstractWriter будет представлять абстракцию для записи в некоторый контекст (будь то XML-документ или текстовый файл).

Public abstract class AbstractWriter { public abstract void write(Object context); }

У этого класса может быть любое кол-во наследников. Рассмотрим подклассы ConcreteFileWriter и ConcreteXmlWriter для записи в текстовый файл и DOM документ соответственно:

Public class ConcreteFileWriter extends AbstractWriter { public void write (Object context) { // method body } } public class ConcreteXmlWriter extends AbstractWriter { public void write (Object context) { // method body } }

Для создания нужного нам объекта можно написать следующую Фабрику:

Import java.io.File; import org.w3c.dom.Document; public class FactoryMethod { public AbstractWriter getWriter(Object object) { AbstractWriter writer = null; if (object instanceof File) { writer = new ConcreteFileWriter(); } else if (object instanceof Document) { writer = new ConcreteXmlWriter(); } return writer; } }

В тексте программы при создании отчета нужно передать в функцию getWriter объект File или DOM документ. В результате выполнения метода мы получим нужный объект необходимого уровня абстракции.

Используйте паттерн Factory в следующих случаях:

  • класс не имеет информации о том, какой тип объекта он должен создать;
  • класс передает ответственность по созданию объектов наследникам;
  • необходимо создать объект в зависимости от входящих данных.

В одной из последующих статей по проектированию мы рассмотрим паттерн Abstract Factory .

А пока что я с удовольствием выслушаю ваши вопросы, замечания и комментарии.



Комментариев: 5

Насколько я понял в примере (за примеры в цикле о паттернах отдельное спасибо) проиллюстрирована параметрирозованная фабрика, т. к. в фабричный метод передаётся параметр, на основе которого и создаётся конкретный подкласс AbstractWriter. Между тем, классическая фабрика, насколько я понял, ведёт себя несколько иначе: “класс спроектирован так, чтобы объекты, которые он создает, специфицировались подклассами” (Э. Гамма ‘Паттерны проектирования’). То есть должно быть несколько наследников FactoryMethod для каждого наследника AbstractWriter, а выбор остаётся за клиентом, какую из реализацию FactoryMethod выбрать. Я правильно понял?

На самом деле в примере показан вовсе не Factory Method, а так называемая Simple Factory (это даже не шаблон проектирования, а просто широко используемая техника). Правильно написал danik, что у класса FactoryMethod должны быть наследники, которые собственно и переопределяют фабричный метод. На первый взгляд может показаться, что нет никакого отличия, но отличие огромное. Прочитайте “Head First Design Patterns” или “Design Patterns For Dummies” и вам станет все понятно.

Ты неправ. Это все таки фабричный метод. Читаем у Эриха Гаммы: “параметризованные фабричные методы. Это еще один вариант паттерна, который позволяет фабричному методу создавать разные виды продуктов. Фабричному методу передается параметр, который идентифицирует вид создаваемого объекта.

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

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

Чего то не понял, а чем это отличается он паттерна стратегия?

Можно я тоже по умничаю. antonin yorov разница между фабрикой и стратегией в том что стратегия позволяет динамически определять нужный алгоритм (то есть можно в рантайме подключать нужный субкласс интерфейса) а фабрика при всем этом в алгоритме имеет метод который возвращает объект.

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

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

Пример

Предположим мы создаем некий XML парсер, который анализирует предоставленный файл и преобразует его в DOM дерево. Каждый элемент этого дерева назовем нодой (Node). В время разбора файла, перед нами встанет задача порождения новых нод, и мы напишем там примерно такой код:

Class Xml_Node() { /*...*/ public function parse() { /*...*/ $ChildNode = new Xml_Node(); /*...*/ } /*...*/ }

Что в этом плохого? Приведу такой пример: мы захотим на основе XML файла строить структуру объектов, определенного класса, чтобы использовать ее в дальнейшем, и нам, в соответствии с принципом "до тебя уже все написано", захотелось использовать готовый класс XML_Node .

Мы делаем своего наследника XML_Node_Processor , и хотим теперь повлиять на процесс анализа файла так, чтобы при определенном теге инстанцировался определенный класс (Для тега food - My_Food , для cow - My_Big_Orange_Cow). И при реализации как приведена выше, для этого нам придется полностью перегрузить метод parse, ради того, чтобы сделать копипаст кода из родительского класса отредактировав всего одну строку кода. Согласитесь, это глупо.

Суть паттерна

Возможная реализация на PHP

Abstract class XML_Node_Abstract { abstract function createNode($tag); } class Xml_Node extends XML_Node_Abstract { /*...*/ public function createNode($tag) { return new Xml_Node(); } /*...*/ public function parse() { /*...*/ $ChildNode = $this -> createNode($Tag); /*..*/ } } class Xml_Node_Processor extends Xml_Node { public function createNode($tag) { switch($tag) { case "food": return new My_Food(); case "cow": return new My_Big_Orange_Cow(); } return parent::createNode($tag); } } class My_Food extends Xml_Node_Processor {}; class My_Big_Orange_Cow extends Xml_Node_Processor {};

В заключение

  • В реализации фабричного метода не всегда нужен абстрактный класс создателя (XML_Node_Abstract). На его месте может использоваться конкретный экземпляр. Из этого примера можно выкинуть XML_Node_Abstract и ничего не изменится
  • Результат возвращаемый фабричным методом, должен всегда соответствовать заданному интерфейсу (в нашем случае интерфейсу класса Xml_Node)
  • Фабричный метод может быть статической функцией, и использоваться для инстанации объектов подкласса
  • Фабричный метод не обязательно должен возвращать объект, он так же может возвращать класс. При этом все наследники и родители так же должны возвращать класс.

Фактически состоит из фабричных методов

Дополнено

Вопрос

Не понял. Смысл в том, чтобы в методе parse наследников создавались экземпляры именно их, а не родителя?

Почему бы вместо:

$ChildNode = new Xml_Node () ;

не сделать:

$ChildNode = new static; ?

Ответ

new static не решает проблему, решение которой возложено на фабричный метод. Его основная задача убрать зависимость из кода, зависимость от конкретного класса. Казалось бы, что плохого в этом? Ничего. Ровно до той поры, пока не потребуется расширить класс, внести некоторую логику или наладить модульное тестирование.

Вот представьте, у вас есть такой код в нескольких местах:

$node = new Xml_Node (); $title = $node->getTitle ();

Приходит к вам проект менеджер и говорит, что xml будут приходить в двух разных форматах. Подумаешь тоже:

If ($this -> isFormatOne ()) { $node = new Xml_Node (); } else { $node = new Xml_Node_Extended (); } $title = $node -> getTitle ();

Затем он приходит снова, и говорит, что форматов теперь будет 3,10,500. При такой архитектуре, придется КАЖДЫЙ раз вносить изменения во ВСЕ вхождения такого кода. Если же использовать фабричный метод, то придется изменить только его, а создания объекта будет выглядеть всегда одинково:

$node = $this -> createNode (); $title = $node -> getTitle ();

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

Фабричный метод - это порождающий паттерн проектирования, который решает проблему создания различных продуктов, без указания конкретных классов продуктов.

Фабричный метод задаёт метод, который следует использовать вместо вызова оператора new для создания объектов-продуктов. Подклассы могут переопределить этот метод, чтобы изменять тип создаваемых продуктов.

Особенности паттерна на Java

Сложность:

Популярность:

Применимость: Паттерн можно часто встретить в любом Java-коде, где требуется гибкость при создании продуктов.

Паттерн широко используется в стандартных библиотеках Java:

  • java.net.URLStreamHandlerFactory#createURLStreamHandler(String) (Возвращает разные объекты-одиночки, в зависимости от протокола)
  • javax.xml.bind.JAXBContext#createMarshaller() и другие похожие методы.

Признаки применения паттерна: Фабричный метод можно определить по создающим методам, которые возвращают объекты продуктов через абстрактные типы или интерфейсы. Это позволяет переопределять типы создаваемых продуктов в подклассах.

Производство кросс-платформенных элементов GUI

В этом примере в роли продуктов выступают кнопки, а в роли создателя - диалог.

Разным типам диалогов соответствуют свои собственные типы элементов. Поэтому для каждого типа диалога мы создаём свой подкласс и переопределяем в нём фабричный метод.

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

buttons

buttons/Button.java: Общий интерфейс кнопок

package сайт.factory_method.example.buttons; /** * Общий интерфейс для всех продуктов. */ public interface Button { void render(); void onClick(); }

buttons/HtmlButton.java: Конкретный класс кнопок

package сайт.factory_method.example.buttons; /** * Реализация HTML кнопок. */ public class HtmlButton implements Button { public void render() { System.out.println(""); onClick(); } public void onClick() { System.out.println("Click! Button says - "Hello World!""); } }

buttons/WindowsButton.java: Ещё один класс кнопок

package сайт.factory_method.example.buttons; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; /** * Реализация нативных кнопок операционной системы. */ public class WindowsButton implements Button { JPanel panel = new JPanel(); JFrame frame = new JFrame(); JButton button; public void render() { frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JLabel label = new JLabel("Hello World!"); label.setOpaque(true); label.setBackground(new Color(235, 233, 126)); label.setFont(new Font("Dialog", Font.BOLD, 44)); label.setHorizontalAlignment(SwingConstants.CENTER); panel.setLayout(new FlowLayout(FlowLayout.CENTER)); frame.getContentPane().add(panel); panel.add(label); onClick(); panel.add(button); frame.setSize(320, 200); frame.setVisible(true); onClick(); } public void onClick() { button = new JButton("Exit"); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { frame.setVisible(false); System.exit(0); } }); } }

factory

factory/Dialog.java: Базовый диалог

package сайт.factory_method.example..factory_method.example.buttons.Button; /** * Базовый класс фабрики. Заметьте, что "фабрика" – это всего лишь * дополнительная роль для класса. Он уже имеет какую-то бизнес-логику, в * которой требуется создание разнообразных продуктов. */ public abstract class Dialog { public void renderWindow() { // ... остальной код диалога... Button okButton = createButton(); okButton.render(); } /** * Подклассы будут переопределять этот метод, чтобы создавать конкретные * объекты продуктов, разные для каждой фабрики. */ public abstract Button createButton(); }

factory/HtmlDialog.java: Конкретный класс диалогов

package сайт.factory_method.example..factory_method.example.buttons..factory_method.example.buttons.HtmlButton; /** * HTML-диалог. */ public class HtmlDialog extends Dialog { @Override public Button createButton() { return new HtmlButton(); } }

factory/WindowsDialog.java: Ещё один класс диалогов

package сайт.factory_method.example..factory_method.example.buttons..factory_method.example.buttons.WindowsButton; /** * Диалог на элементах операционной системы. */ public class WindowsDialog extends Dialog { @Override public Button createButton() { return new WindowsButton(); } }

Demo.java: Клиентский код

package сайт.factory_method..factory_method.example.factory..factory_method.example.factory.Html.factory_method.example.factory.WindowsDialog; /** * Демо-класс. Здесь всё сводится воедино. */ public class Demo { private static Dialog dialog; public static void main(String args) { configure(); runBusinessLogic(); } /** * Приложение создаёт определённую фабрику в зависимости от конфигурации или * окружения. */ static void configure() { if (System.getProperty("os.name").equals("Windows 10")) { dialog = new WindowsDialog(); } else { dialog = new HtmlDialog(); } } /** * Весь остальной клиентский код работает с фабрикой и продуктами только * через общий интерфейс, поэтому для него неважно какая фабрика была * создана. */ static void runBusinessLogic() { dialog.renderWindow(); } }

OutputDemo.txt: Результат с фабрикой HtmlDialog

Click! Button says - "Hello World!"

Назначение паттерна Factory Method

В системе часто требуется создавать объекты самых разных типов. Паттерн Factory Method (фабричный метод) может быть полезным в решении следующих задач:

  • Система должна оставаться расширяемой путем добавления объектов новых типов. Непосредственное использование выражения new является нежелательным, так как в этом случае код создания объектов с указанием конкретных типов может получиться разбросанным по всему приложению. Тогда такие операции как добавление в систему объектов новых типов или замена объектов одного типа на другой будут затруднительными (подробнее в разделе Порождающие паттерны). Паттерн Factory Method позволяет системе оставаться независимой как от самого процесса порождения объектов, так и от их типов.
  • Заранее известно, когда нужно создавать объект, но неизвестен его тип.

Описание паттерна Factory Method

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

Для обеспечения относительно простого добавления в систему новых типов паттерн Factory Method локализует создание объектов конкретных типов в специальном классе-фабрике. Методы этого класса, посредством которых создаются объекты конкретных классов, называются фабричными. Существуют две разновидности паттерна Factory Method:

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

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

Реализация паттерна Factory Method

Рассмотрим оба варианта реализации паттерна Factory Method на примере процесса порождения военных персонажей для нашей стратегической игры. Ее подробное описание можно найти в разделе Порождающие паттерны . Для упрощения демонстрационного кода будем создавать военные персонажи для некой абстрактной армии без учета особенностей воюющих сторон.

Реализация паттерна Factory Method на основе обобщенного конструктора

// #include #include enum Warrior_ID { Infantryman_ID=0, Archer_ID, Horseman_ID }; // Иерархия классов игровых персонажей class Warrior { public: virtual void info() = 0; virtual ~Warrior() {} // Параметризированный статический фабричный метод static Warrior* createWarrior(Warrior_ID id); }; class Infantryman: public Warrior { public: void info() { cout << "Infantryman" << endl; } }; class Archer: public Warrior { public: void info() { cout << "Archer" << endl; } }; class Horseman: public Warrior { public: void info() { cout << "Horseman" << endl; } }; // Реализация параметризированного фабричного метода Warrior* Warrior::createWarrior(Warrior_ID id) { Warrior * p; switch (id) { case Infantryman_ID: p = new Infantryman(); break; case Archer_ID: p = new Archer(); break; case Horseman_ID: p = new Horseman(); break; default: assert(false); } return p; }; // Создание объектов при помощи параметризированного фабричного метода int main() { vector v; v.push_back(Warrior::createWarrior(Infantryman_ID)); v.push_back(Warrior::createWarrior(Archer_ID)); v.push_back(Warrior::createWarrior(Horseman_ID)); for(int i=0; iinfo(); // ... }

Представленный вариант паттерна Factory Method пользуется популярностью благодаря своей простоте. В нем статический фабричный метод createWarrior() определен непосредственно в полиморфном базовом классе Warrior . Этот фабричный метод является параметризированным, то есть для создания объекта некоторого типа в createWarrior() передается соответствующий идентификатор типа.

С точки зрения "чистоты" объектно-ориентированного кода у этого варианта есть следующие недостатки:

  • Так как код по созданию объектов всех возможных типов сосредоточен в статическом фабричном методе класса Warrior , то базовый класс Warrior обладает знанием обо всех производных от него классах, что является нетипичным для объектно-ориентированного подхода.
  • Подобное использование оператора switch (как в коде фабричного метода createWarrior()) в объектно-ориентированном программировании также не приветствуется.

Указанные недостатки отсутствуют в классической реализации паттерна Factory Method.

Классическая реализация паттерна Factory Method

// #include #include // Иерархия классов игровых персонажей class Warrior { public: virtual void info() = 0; virtual ~Warrior() {} }; class Infantryman: public Warrior { public: void info() { cout << "Infantryman" << endl; }; }; class Archer: public Warrior { public: void info() { cout << "Archer" << endl; }; }; class Horseman: public Warrior { public: void info() { cout << "Horseman" << endl; }; }; // Фабрики объектов class Factory { public: virtual Warrior* createWarrior() = 0; virtual ~Factory() {} }; class InfantryFactory: public Factory { public: Warrior* createWarrior() { return new Infantryman; } }; class ArchersFactory: public Factory { public: Warrior* createWarrior() { return new Archer; } }; class CavalryFactory: public Factory { public: Warrior* createWarrior() { return new Horseman; } }; // Создание объектов при помощи фабрик объектов int main() { InfantryFactory* infantry_factory = new InfantryFactory; ArchersFactory* archers_factory = new ArchersFactory ; CavalryFactory* cavalry_factory = new CavalryFactory ; vector v; v.push_back(infantry_factory->createWarrior()); v.push_back(archers_factory->createWarrior()); v.push_back(cavalry_factory->createWarrior()); for(int i=0; iinfo(); // ... }

Классический вариант паттерна Factory Method использует идею полиморфной фабрики. Специально выделенный для создания объектов полиморфный базовый класс Factory объявляет интерфейс фабричного метода createWarrior() , а производные классы его реализуют.

Представленный вариант паттерна Factory Method является наиболее распространенным, но не единственным. Возможны следующие вариации:

  • Класс Factory имеет реализацию фабричного метода createWarrior() по умолчанию.
  • Фабричный метод createWarrior() класса Factory параметризирован типом создаваемого объекта (как и у представленного ранее, простого варианта Factory Method) и имеет реализацию по умолчанию. В этом случае, производные от Factory классы необходимы лишь для того, чтобы определить нестандартное поведение createWarrior() .

Результаты применения паттерна Factory Method

Достоинства паттерна Factory Method

  • Создает объекты разных типов, позволяя системе оставаться независимой как от самого процесса создания, так и от типов создаваемых объектов.

Недостатки паттерна Factory Method

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