Шаблон фабричный метод. Фабричный метод - паттерн (шаблон) проектирования - описание. Схожие шаблоны и их отличия

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

Фабричный метод задаёт метод, который следует использовать вместо вызова оператора 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 дает возможность подклассам создавать некоторые классы с помощью общего интерфейса, причем именно наследники определяют, какой родительский объект следует реализовать. То есть, нужен какой то, общий интерфейс. Этим интерфейсом в языке программирования C# может быть абстрактный класс либо интерфейс.

Представьте себе такой абстрактный класс:

Abstract class Product { public abstract decimal PurchasePrice {get; set;} public abstract decimal Price {get; set;} public abstract string Description {get; set;} }

Если мы унаследуем этот класс, то обязаны придерживаться принципа полиморфизма. То есть, переопределить все свойства этого класса, используя слово override. Давайте так и сделаем. Создаем класс, унаследованный от класса Product, который будет инкапсулировать логику конкретного продукта:

Class Computer: Product { private decimal _purchase_price; private decimal _price; private string _description; public Computer() : this(null) { } public Computer(string _description) : this(_description, 0) { } public Computer(string _description, decimal _purchase_price) : this (_description, _purchase_price, 0) { } 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; } } }

Класс Product - предназначен для определения интерфейса объектов, создаваемых фабричным методом. Это как бы базовая оболочка для продуктов. Продукт имеет цену и т.д. Если хотите, допишите в класс Product еще пару свойств, методов, и переопределите их в наследуемом классе. Надеюсь что пока все ясно. Прежде чем я продолжу, скажу пару слов о самом паттерне. Обычно мы используем конкретный класс и пишем вот так:

Computer computer = new Computer();

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

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

Вот что имеется ввиду:

На диаграмме добавилось еще два класса. Следуя шаблону, должен быть еще один абстрактный класс, в котором и будет фабричный метод. Это как бы фабрика, которая создает продукты конкретного вида. Когда определен абстрактный класс Creator с фабричным методом, мы можем создать свой унаследованный класс для конкретного продукта. Чтоб легче было понять, упростим диаграмму:

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

Вот какой вид имеет абстрактный класс Creator :

Abstract class Creator { public abstract Product FactoryMethod(); public abstract Product FactoryMethod(string _description); public abstract Product FactoryMethod(string _description, decimal _purchase_price); public abstract Product FactoryMethod(string _description, decimal _purchase_price, decimal _price); } В этом классе, я определил методы для всех видов конструкторов класса Computer. А вот создатель-класс для класса Computer: class ComputerCreator: Creator { public override Product FactoryMethod() { return new Computer(); } public override Product FactoryMethod(string _description) { return new Computer(_description); } public override Product FactoryMethod(string _description, decimal _purchase_price) { return new Computer(_description, _purchase_price); } public override Product FactoryMethod(string _description, decimal _purchase_price, decimal _price) { return new Computer(_description,_purchase_price,_price); } }

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

Создадим еще один класс CDPlayer и класс создатель для него аналогичным образом:

Класс CDPlayer :

Class CDPlayer: Product { private decimal _purchase_price; // цена закупки private decimal _price; // цена продажи // масив форматов, которые поддерживет сд плеер private string _description; public CDPlayer() : this(null) { } public CDPlayer(string _description) : this(_description, 0) { } public CDPlayer(string _description, decimal _purchase_price) : this(_description, _purchase_price, 0) { } public CDPlayer(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;} } }

Создатель-класс для класса CDPlayer :

Class CDPlayerCreator: Creator { public override Product FactoryMethod() { return new CDPlayer(); } public override Product FactoryMethod(string _description) { return new CDPlayer(_description); } public override Product FactoryMethod(string _description, decimal _purchase_price) { return new CDPlayer(_description, _purchase_price); } public override Product FactoryMethod(string _description, decimal _purchase_price, decimal _price) { return new CDPlayer(_description, _purchase_price, _price); } }

Все что нам осталось, это написать клиентский код, что бы полюбоваться нашей работой.

Static void Main(string args) { ListProductList = new List(); Creator creators = new Creator; creators = new ComputerCreator(); creators = new CDPlayerCreator(); foreach (Creator cr in creators) { if (cr is ComputerCreator) productList.Add(cr.FactoryMethod("Ноут бук", 600, 800)); if (cr is CDPlayerCreator) productList.Add(cr.FactoryMethod("audio,mp3,mp4",250,360)); } foreach (Product pr in productList) { Console.WriteLine("Обьект класса {0};\n" + "Описание: {1};\n" + "Закупочная цена: {2};\n" + "Цена продажы: {3};\n", pr.GetType().Name, pr.Description, pr.PurchasePrice, pr.Price); } Console.ReadLine(); }

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

Как по мне, то тут интересный факт. Экземпляр абстрактного класса создать нельзя. Но никто не говорил, что он не может служить как базовый. Если написать следующим образом, то все будет в порядке:

Product pr = new Computer();

Здесь ссылка pr (абстрактного класса) ссылается на объект класса Computer . По этой причине я спокойно могу создать специализированную коллекцию, как это и было сделано. Когда я увидел этот шаблон, я сразу заметил такие стадии разработки:

  • Абстрактный класс для объектов –> напротив класс, который его реализует для своих целей.
  • Абстрактный класс для создания объектов –> напротив класс, который его реализует для создания своих обьектов.
Иными словами:

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

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

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

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

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

Теперь отметим плюсы и минусы данного паттерна:

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

Фабричный метод - паттерн, порождающий классы - относится к порождающим паттернам (шаблонам)

Назначение

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

Псевдоним

Паттерн Фабричный метод известен также под именем VirtualConstructor (виртуальный конструктор)

Мотивация

Пусть у нас есть приложение - ну или мы хотим его написать - причём такое что оно может создавать документы разных типов - но мы не знаем заранее какой тип документа (= продукта) выберет пользователь -
и тем не менее механизм создания этих документов - если смотреть "снаружи" выглядит сходно - эта схожесть описывается абстрактными классами, при этом абстрактные классы инстацировать нельзя - то есть нельзя создать объекты этих классов.
Как же быть?

Решение нам предлагает паттерн Фабричный метод , который прячет имя конкретного документа в классе приложения, которое его создаёт.

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

Кстати это более наглядно демонстрирует такая вот вольная диаграмма:

Применимость

Используйте паттерн фабричный метод, когда:

  1. классу заранее неизвестно, объекты каких классов ему нужно создавать;
  2. класс спроектирован так, чтобы объекты, которые он создает, специфицировались подклассами ;
  3. класс делегирует свои обязанности одному из нескольких вспомогательных подклассов, и вы планируете локализовать знание о том, какой класс принимает эти обязанности на себя.

Структура

Структуру данного шаблона можно представить в виде следующей диаграммы:

Участники

  1. Product (Document) - продукт: определяет интерфейс объектов, создаваемых фабричным методом;
  2. ConcreteProduct (MyDocument) конкретный продукт: реализует интерфейсProduct;
  3. Creator (Application) = создатель: объявляет фабричный метод, возвращающий объект типаProduct. Creator может также определять реализацию по умолчанию фабричного метода, который возвращает объект ConcreteProduct; может вызывать фабричный метод для создания объекта Product.
  4. ConcreteCreator (MyApplication) = конкретный создатель: замещает фабричный метод, возвращающий объект ConcreteProduct.

Отношения

Создатель «полагается» на свои подклассы (конкретные реализации Creator) в определении фабричного метода, который будет возвращать экземпляр подходящего конкретного продукта.

Результаты

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

Потенциальный недостаток

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

Ещё два возможных применения паттерна фрабричный метод:

Предоставление подклассам операций-зацепок (hooks)

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

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

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

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

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

При таких ограничениях лучше использовать отдельный объект манипулятор Manipulator, который реализует взаимодействие и контролирует его текущее состояние. У разных фигур будут разные манипуляторы, являющиеся подклассом Manipulator. Получающаяся иерархия класса Manipulator параллельна(по крайней мере, частично) иерархии класса Figure. Класс Figure предоставляет фабричный метод CreateManipulator, который позволяет клиентам создавать соответствующий фигуре манипулятор. Подклассы Figure замещают этот метод так, чтобы он возвращал подходящий для них подкласс Manipulator. Вместо этого класс Figure может реализовать CreateManipulator так, что он будет возвращать экземпляр классаManipulator по умолчанию, а подклассыFigure могут наследовать
это умолчание.

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

Вот схема:

Итак ещё раз (о результатах):

  1. Фабричные методы избавляют проектировщика от необходимости встраивать в код зависящие от приложения классы
  2. Можно создавать (при желании) расширенные объекты - предоставив конкретным реализациям абстрактного Creator возможно переопределить - в случае необходимости ряд методов (как в примере с файловым диалогом)
  3. Соединения параллельных иерархий

Реализация

В этом разделе следует упомянуть об особенностях реализации, они таковы:

  • Выделяют два принципиальных случая для релазации:
    1. когда класс создатель является абстрактным
    2. когда Creator(создатель) - конкретный ("обычный") класс
    3. ну и всё же встречается смешанный тип - когда абстрактный класс содержит реализацию по-умолчанию
  • параметризованные фабричные методы - данная особенность подразумевает, что вообще говоря - с помощью фабричного метода можно создавать разные виды продуктов в зависимости от переданных параметров
  • различные особенности связанные с конкретным языком реализации

Пример кода

Известные применения

Фабричные методы где только не встречаются - большинство библиотек и каркасов так или иначе используют паттерн Фабричный метод - в частности библиотека ЕТ++

Родственные паттерны

Часто реализуется с помощью фабричных методов.

Пример в разделе «Мотивация» из описания абстрактной фабрики иллюструет также и паттерн фабричные методы.
Паттерн фабричный метод часто вызывается внутри шаблонных методов.

Прототипы не нуждаются в порождении подклассов от класса Creator. Однако им часто бывает необходима операция Initialize в классе Product.
Creator использует Initialize для для инициализации объекта. Фабричному
методу такая операция не требуется.

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

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

Пример

Предположим мы создаем некий 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 ();

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

Назначение паттерна 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

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