Фабричный метод (англ. Factory Method также известен как Виртуальный конструктор (англ. Virtual Constructor)) - порождающий шаблон проектирования, предоставляющий подклассам интерфейс для создания экземпляров некоторого класса. В момент создания наследники могут определить, какой класс создавать. Иными словами, Фабрика делегирует создание объектов наследникам родительского класса. Это позволяет использовать в коде программы не специфические классы, а манипулировать абстрактными объектами на более высоком уровне.
Определяет интерфейс для создания объекта, но оставляет подклассам решение о том, какой класс инстанцировать. Фабричный метод позволяет классу делегировать создание подклассов. Используется, когда:
Структура
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 разница между фабрикой и стратегией в том что стратегия позволяет динамически определять нужный алгоритм (то есть можно в рантайме подключать нужный субкласс интерфейса) а фабрика при всем этом в алгоритме имеет метод который возвращает объект.
Название и классификация паттерна
Фабричный метод - паттерн, порождающий классы.
Назначение
Определяет интерфейс для создания объекта, но оставляет подклассам решение о том, какой класс инстанцировать. Фабричный метод позволяет классу делегировать инстанцирование подклассам.
Известен также под именем Virtual Constructor (виртуальный конструктор).
Применимость паттерна Factory Method
В системе часто требуется создавать объекты самых разных типов. Паттерн Factory Method может быть полезным, если система должна оставаться расширяемой путем добавления объектов новых типов. Непосредственное использование выражения new является нежелательным, так как в этом случае код создания объектов с указанием конкретных типов может получиться разбросанным по всему приложению. Тогда такие операции, как добавление в систему объектов новых типов или замена объектов одного типа на другой, будут затруднительными. Паттерн Factory Method позволяет системе оставаться независимой как от самого процесса порождения объектов, так и от их типов.
Описание паттерна Factory Method
Для того чтобы система оставалась независимой от различных типов объектов, паттерн Factory Method использует механизм полиморфизма - классы всех конечных типов наследуют от одного абстрактного базового класса, предназначенного для полиморфного использования. В этом базовом классе определяется единый интерфейс, через который пользователь будет оперировать объектами конечных типов.
Для обеспечения относительно простого добавления в систему новых типов паттерн Factory Method локализует создание объектов конкретных типов в специальном классе-фабрике. Методы этого класса, посредством которых создаются объекты конкретных классов, называются фабричными.
Структура
Существуют две разновидности паттерна Factory Method.
Обобщенный конструктор, когда в том же самом полиморфном базовом классе, от которого наследуют производные классы всех создаваемых в системе типов, определяется статический фабричный метод. В качестве параметра в этот метод должен передаваться идентификатор типа создаваемого объекта (рис. 32).
Классический вариант фабричного метода, когда интерфейс фабричных методов объявляется в независимом классе-фабрике, а их реализация определяется конкретными подклассами этого класса (рис. 33).
Подклассы класса Creator переопределяют абстрактную операцию Factory Method таким образом, чтобы она возвращала подходящий под-
Рис. 32.
Обобщенный конструктор
Рис. 33.
Классическая реализация
return newConcreteProduct
класс класса Concrete Product. Как только подкласс Creator будет инстанцирован, он может инстанцировать специфические для приложения документы, ничего не зная об их классах. Операцию Factory Method называют фабричным методом, поскольку она отвечает за «изготовление» объекта.
Участники
Product (продукт) - определяет интерфейс объектов, создаваемых фабричным методом.
ConcreteProduct (конкретный продукт) - реализует интерфейс Product.
Creator (создатель) - объявляет фабричный метод, возвращающий объект типа Product. Creator может также определять реализацию по умолчанию фабричного метода, который возвращает объект ConcreteProduct.
Может вызывать фабричный метод для создания объекта Product.
ConcreteCreator (конкретный создатель) - замещает фабричный метод, возвращающий объект ConcreteProduct.
Отношения
Создатель «полагается» на свои подклассы в определении фабричного метода, который будет возвращать экземпляр подходящего конкретного продукта.
Результаты
Фабричные методы избавляют проектировщика от необходимости встраивать в код зависящие от приложения классы. Код имеет дело только с интерфейсом класса Product, поэтому он может работать с любыми определенными пользователями классами конкретных продуктов.
Потенциальный недостаток фабричного метода состоит в том, что клиентам, возможно, придется создавать подкласс класса Creator для создания лишь одного объекта ConcreteProduct. Порождение подклассов оправданно, если клиенту так или иначе приходится создавать подклассы Creator, в противном случае клиенту придется иметь дело с дополнительным уровнем подклассов.
Пример кода
Рассмотрим оба варианта реализации паттерна Factory Method на примере процесса порождения военных персонажей для описанной ранее стратегической игры. Для упрощения демонстрационного кода будем создавать военные персонажи для некой абстрактной армии без учета особенностей воюющих сторон.
Реализация паттерна Factory Method на основе обобщенного конструктора
enum Warrior_ID {Infantryman_ID=0, Archer_ID, Horseman_ID };
// Иерархия классов игровых персонажей class Warrior {
// Параметризированный статический фабричный метод static Warrior* createWarrior(Warrior_ID id);
public: void info() {
class Archer: public Warrior
public: void info() {
public: void info() {
// Реализация параметризированного фабричного метода 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);
// Создание объектов при помощи параметризированного фабричного
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; i info();
Представленный вариант паттерна Factory Method пользуется популярностью благодаря своей простоте. В нем статический фабричный метод createWarrior() определен непосредственно в полиморфном базовом классе Warrior. Этот фабричный метод является пара-метризированным, т. е. для создания объекта некоторого типа в createWarriorQ передается соответствующий идентификатор типа.
С точки зрения «чистоты» объектно-ориентированного кода у этого варианта есть следующие недостатки:
Указанные недостатки отсутствуют в классической реализации паттерна Factory Method.
Классическая реализация паттерна Factory Method
// Иерархия классов игровых персонажей
virtual void info() = 0; virtual ~Warrior() {}
class Infantryman: public Warrior
public: void info() {
class Archer: public Warrior
public: void info() {
class Horseman: public Warrior
public: void info() {
// Фабрики объектов class Factory
virtual Warrior* createWarrior() = 0; virtual ~Factory() {}
class Infantry Factory: public Factory
Warrior* createWarrior() { return new Infantryman;
class ArchersFactory: public Factory
Warrior* createWarrior() { return new Archer;
class CavalryFactory: public Factory
Warrior* createWarrior() { return new Horseman;
// Создание объектов при помощи фабрик объектов int main()
InfantryFactory* infantry_factory = new Infantry Factory; ArchersFactory* archers_factory = new ArchersFactory ; CavalryFactory* cavalry_factory = new CavalryFactory ;
v.push_back(infantry_factory->createWarrior()); v.push_back(archers_factory->createWarrior()); v.push_back(cavalry_factory->createWarrior());
for(int i=0; i info();
Классический вариант паттерна Factory Method использует идею полиморфной фабрики. Специально выделенный для создания объектов полиморфный базовый класс Factory объявляет интерфейс фабричного метода createWarrior(), а производные классы его реализуют.
Представленный вариант паттерна Factory Method является наиболее распространенным, но не единственным. Возможны следующие вариации:
Достоинства паттерна Factory Method
Создает объекты разных типов, позволяя системе оставаться независимой как от самого процесса создания, так и от типов создаваемых объектов.
Недостатки паттерна Factory Method
В случае классического варианта паттерна даже для порождения единственного объекта необходимо создавать соответствующую фабрику.
Родственные паттерны
Абстрактная фабрика часто реализуется с помощью Фабричных методов.
Паттерн Фабричный метод часто вызывается внутри Шаблонных методов.
Название и классификация паттерна
Абстрактная фабрика - паттерн, порождающий объекты.
Назначение
Предоставляет интерфейс для создания семейств взаимосвязанных или взаимозависимых объектов, не специфицируя их конкретных классов.
Применимость
Использование паттерна Abstract Factory (абстрактная фабрика) целесообразно, если:
Приведем примеры групп взаимосвязанных объектов.
Пусть некоторое приложение с поддержкой графического интерфейса пользователя рассчитано на использование на различных платформах, при этом внешний вид этого интерфейса должен соответствовать принятому стилю для той или иной платформы. Например, если это приложение установлено на Windows-платформу, то его кнопки, меню, полосы прокрутки должны отображаться в стиле, принятом для Windows. Группой взаимосвязанных объектов в этом случае будут элементы графического интерфейса пользователя для конкретной платформы.
Другой пример. Рассмотрим текстовый редактор с многоязычной поддержкой, у которого имеются функциональные модули, отвечающие за расстановку переносов слов и проверку орфографии. Если открыт документ на русском языке, то должны быть подключены соответствующие модули, учитывающие специфику русского языка. Ситуация, когда для такого документа одновременно используются модуль расстановки переносов для русского языка и модуль проверки орфографии для немецкого языка, исключается. Здесь группой взаимосвязанных объектов будут соответствующие модули, учитывающие специфику некоторого языка.
И последний пример. Выше говорилось об игре-стратегии «Пунические войны». Очевидно, что внешний вид, боевые порядки и характеристики для разных родов войск (пехота, лучники, конница) в каждой армии будут своими. В данном случае семейством взаимосвязанных объектов будут все виды воинов для той или иной противоборствующей стороны, при этом должна исключаться, например, такая ситуация, когда римская конница воюет на стороне Карфагена.
Структура
Структура паттерна Абстрактная фабрика показана на рис. 35.
Рис. 35.
Участники
AbstractFactory - абстрактная фабрика: объявляет интерфейс для операций, создающих абстрактные объекты-продукты.
ConcreteFactory (ConcreteFactoryl, ConcreteFactory2) - конкретная фабрика: реализует операции, создающие конкретные объекты-продукты (для игры «Пунические войны» создаются армии Рима и Карфагена).
AbstractProduct (Abstract Product A, Abstract Product В) - абстрактный продукт: объявляет интерфейс для типа объекта-продукта.
ConcreteProduct (ProductA, Product В) - конкретный продукт: определяет объект-продукт, создаваемый соответствующей конкретной фабрикой (например, лучник, всадник), - реализует интерфейс Abstract Product.
Client - клиент: пользуется исключительно интерфейсами, которые объявлены в классах AbstractFactory и AbstractProduct.
Отношения
Обычно во время выполнения создается единственный экземпляр класса ConcreteFactory. Эта конкретная фабрика создает объекты-продукты, имеющие вполне определенную реализацию. Для создания других видов объектов клиент должен воспользоваться другой конкретной фабрикой.
Abstract Factory передоверяет создание объектов-продуктов своему подклассу ConcreteFactory.
Результаты
Паттерн Абстрактная фабрика обладает следующими плюсами и минусами:
Пример кода для паттерна Abstract Factory
Приведем реализацию паттерна Abstract Factory для военной стратегии «Пунические войны». При этом предполагается, что число и типы создаваемых в игре боевых единиц идентичны для обеих армий. Каждая армия имеет в своем составе пехотинцев (Infantryman), лучников (Archer) и кавалерию (Horseman).
Структура паттерна для данного случая представлена на рис. 36.
ArmyFactory
Infantryman
Roman Infantryman
Carthaginianlnfantryman
InfantrymanO
Archer()
Horseman О
- Carthaginian ArmyFactory |
Roman ArmyFactory |
|
Carthaginianlnfantryman() CarthaginianArcher() CarthaginianHorseman() |
RomanInfantryman() |
- -> RomanArcher
Archer
Carthaginian Archer
CarthaginianHorscnian
Рис. 36. UML-диаграмма классов для военной стратегии «Пунические войны»
// Абстрактные базовые классы всех возможных видов воинов class Infantryman
virtual void info() = 0; virtual ~Infantryman() {}
virtual void info() = 0; virtual ~Archer() {}
virtual void info() = 0; virtual ~Horseman() {}
// Классы всех видов воинов римской армии class Romanlnfantryman: public Infantryman {
public: void info() {
class RomanArcher: public Archer
public: void info() {
class RomanHorseman: public Horseman
public: void info() {
// Классы всех видов воинов армии Карфагена class Carthaginianlnfantryman: public Infantryman
public: void info() {
class CarthaginianArcher: public Archer
public: void info() {
class CarthaginianHorseman: public Horseman
public: void info() {
//Абстрактная фабрика для производства воинов class ArmyFactory {
virtual Infantryman* createlnfantryman() = 0; virtual Archer* createArcher() = 0; virtual Horseman* createHorseman() = 0; virtual ~ArmyFactory() {}
// Фабрика для создания воинов римской армии class RomanArmyFactory: public ArmyFactory {
Infantryman* createlnfantryman() { return new Romanlnfantryman;
Archer* createArcher() { return new RomanArcher;
Horseman* createHorseman() { return new RomanHorseman;
// Фабрика для создания воинов армии Карфагена class CarthaginianArmyFactory: public ArmyFactory {
Infantryman* createlnfantryman() { return new Carthaginianlnfantryman;
Archer* createArcherQ { return new CarthaginianArcher;
Horseman* createHorsemanQ { return new Carthaginian Horseman;
~Army() { int i;
void info() { int i;
for(i=0; i info(); for(i=0; i info(); for(i=0; i info();
vector vi; vector va; vector vh;
// Здесь создается армия той или иной стороны class Game
Army* createArmy(ArmyFactory& factory) { Army* p = new Army;
p->vi.push_back(factory.createInfantryman()); p->va.push_back(factory.createArcher()); p->vh.push_back(factory.createHorseman()); return p;
RomanArmyFactory ra_factory; CarthaginianArmyFactory ca_factory;
Army * га = game.createArmy(ra_factory);
Army * са = game.createArmy(ca_factory); co?t info();
Вывод программы будет следующим:
Roman Infantryman
Carthaginian army:
Carthaginianlnfantryman
CarthaginianArcher
Carthaginian Horseman
Достоинства паттерна Abstract Factory
Скрывает сам процесс порождения объектов, а также делает систему независимой от типов создаваемых объектов, специфичных для различных семейств или групп (пользователи оперируют этими объектами через соответствующие абстрактные интерфейсы).
Позволяет быстро настраивать систему на нужное семейство создаваемых объектов. В случае многоплатформенного графического приложения для перехода на новую платформу, т. е. для замены графических элементов (кнопок, меню, полос прокрутки) одного стиля другим, достаточно создать нужный подкласс абстрактной фабрики. При этом условие невозможности одновременного использования элементов разных стилей для некоторой платформы будет выполнено автоматически.
Недостатки паттерна Abstract Factory
Трудно добавлять новые типы создаваемых продуктов или заменять существующие, так как интерфейс базового класса абстрактной фабрики фиксирован. Например, если для нашей стратегической игры нужно будет ввести новый вид военной единицы - осадные орудия, то надо будет добавить новый фабричный метод, объявив его интерфейс в полиморфном базовом классе AbstractFactory и реализовав во всех подклассах. Снять это ограничение можно следующим образом. Все создаваемые объекты должны наследовать от общего абстрактного базового класса, а в единственный фабричный метод в качестве параметра необходимо передавать идентификатор типа объекта, который нужно создать. Однако в этом случае необходимо учитывать следующий момент. Фабричный метод создает объект запрошенного подкласса, но при этом возвращает его с интерфейсом общего абстрактного класса в виде ссылки или указателя, поэтому для такого объекта будет затруднительно выполнить какую-либо операцию, специфичную для подкласса.
Родственные паттерны
Классы Abstract Factory часто реализуются фабричными методами (паттерн Фабричный метод), но могут быть реализованы и с помощью паттерна Прототип. Конкретная фабрика может быть описана паттерном Одиночка.
Фабричный метод (Factory Method).
Порождающий шаблон проектирования (Creational).
Фабричный метод применяется для создания объектов с определенным интерфейсом, реализации которого предоставляются потомками.
Шаблон используется в случаях если:
Фабричный метод | Абстрактная фабрика | Строитель |
---|---|---|
Порождает один объект с определенным интерфейсом. | Порождает семейство объектов с определенными интерфейсами. | Создает в несколько шагов один сложный (составной) объект. |
Метод класса, который переопределяется потомками. | Интерфейс, реализуемый классами. | Интерфейс строителя, реализуемый классами, и класс для управления процессом. |
Скрывает реализацию объекта. | Скрывает реализацию семейства объектов. | Скрывает процесс создания объекта, порождает требуемую реализацию. |
Данный подход обязывает потомка определить свои реализации Фабричного метода и порождаемого им класса.
Рассмотрим на примере класса DocumentManager , отвечающего за работу с документом. Вынесем функции работы с хранилищем, сохранение и загрузку документа, в отдельный интерфейс IDocStorage .
Public interface IDocStorage { void Save(string name, Document document); Document Load(string name); }
В классе DocumentManager добавим абстрактный Фабричный метод CreateStorage() для создания нового хранилища. И, для примера его использования, напишем метод Save(), сохраняющий документ.
Public abstract class DocumentManager { public abstract IDocStorage CreateStorage(); public bool Save(Document document) { if (!this.SaveDialog()) { return false; } // using Factory method to create a new document storage IDocStorage storage = this.CreateStorage(); storage.Save(this._name, document); return true; } }
Определим потомки класса DocumentManager , которые будут сохранять документы в txt и rtf форматах. Реализации IDocStorage разместим в вложенных private классах. Это обеспечит нужный уровень абстракции хранилища, позволив клиентскому коду работать с ними только через интерфейс.
Для краткости, у классов TxtDocStorage и RtfDocStorage убран код их методов.
Public class TxtDocumentManager: DocumentManager { private class TxtDocStorage: IDocStorage { } public override IDocStorage CreateStorage() { return new TxtDocStorage(); } } public class RtfDocumentManager: DocumentManager { private class RtfDocStorage: IDocStorage { } public override IDocStorage CreateStorage() { return new RtfDocStorage(); } }
Теперь результат вызова метода DocumentManager. CreateStorage() будет экземпляром TxtDocStorage или RtfDocStorage . Это будет определяться в зависимости от того, какой потомок абстрактного класса был создан. Значит вызов метода DocumentManager.Save() сохранит данные в соответствующем формате.
// Save a document as txt file using "Save" dialog DocumentManager docManager = new TxtDocumentManager(); docManager.Save(document); // Or use the IDocStorage interface to save a document IDocStorage storage = docManager.CreateStorage(); storage.Save(name, newDocument);
Данный подход почти аналогичен рассмотренному выше варианту. Единственное отличие заключается в том, что базовый класс содержит реализации метода CreateStorage() и интерфейса IDocStorage . Потомки могут как использовать их, так и переопределить, если необходимо изменить функциональность.
Частный случай Фабричного метода. Входной параметр используется для определения, какую реализацию интерфейса требуется создать:
Public enum StorageFormat { Txt, Rtf } public IDocStorage CreateStorage(StorageFormat format) { switch (format) { case StorageFormat.Txt: return new TxtDocStorage(); case StorageFormat.Rtf: return new RtfDocStorage(); default: throw new ArgumentException("An invalid format: " + format.ToString()); } }
Еще один частный случай – использование generics для создания потомков классов. В некоторых случаях это может полностью заменить создание наследников вручную. Например, когда код методов отличается только порождаемым классом.
В C# есть хорошая возможность ограничить типы, используемые в качестве параметра generics, используя ключевое слово where. Так, для класса DocumentManagerGeneric будем требовать наличие IDocStorage и public конструктора без параметров.
Теперь создадим generic-класс, унаследовав его от DocumentManager :
Public class DocumentManagerGeneric
При создании экземпляра этого класса, необходимо указать класс используемого хранилища:
DocumentManager docManager = new DocumentManagerGeneric
В дальнейшем его экземпляр и будет использоваться в методе Save() .
С некоторым допущением, но все же можно отнести к данному шаблону проектирования версию с generic-методом. Здесь нет наследования, но в момент разработки не известно, экземпляры каких классов необходимо будет порождать.
Создадим хранилище, требуемого типа, в метода SetStorage() и сохраним его в закрытом поле:
Public class DocumentManager
{
private IDocStorage _storage;
public void SetStorage
Сам тип становится известен только при разработке кода, использующего класс DocumentManager :
DocumentManager docManager2 = new DocumentManager();
docManager2.SetStorage
Возможно возникнет вопрос, почему просто не передавать хранилище как параметр? Однако, используемый вариант позволяет:
Таком образом уменьшается зависимость класса DocumentManager от внешнего кода и увеличивается контроль над экземпляром класса хранилища. Например, нет необходимости ожидать, что хранилище может быть закрыто клиентским кодом через свою переменную, указывающую на этот же экземпляр.