Фабричный паттерн. Порождающие паттерны

Описание Factory Method

Фабричный метод (англ. Factory Method также известен как Виртуальный конструктор (англ. Virtual Constructor)) - порождающий шаблон проектирования, предоставляющий подклассам интерфейс для создания экземпляров некоторого класса. В момент создания наследники могут определить, какой класс создавать. Иными словами, Фабрика делегирует создание объектов наследникам родительского класса. Это позволяет использовать в коде программы не специфические классы, а манипулировать абстрактными объектами на более высоком уровне.

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

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

Структура

  • Product - продукт
    • определяет интерфейс объектов, создаваемых абстрактным методом;
  • ConcreteProduct - конкретный продукт
    • реализует интерфейс Product ;
  • Creator - создатель
    • объявляет фабричный метод, который возвращает объект типа Product . Может также содержать реализацию этого метода «по умолчанию»;
    • может вызывать фабричный метод для создания объекта типа Product ;
  • ConcreteCreator - конкретный создатель
    • переопределяет фабричный метод таким образом, чтобы он создавал и возвращал объект класса 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 разница между фабрикой и стратегией в том что стратегия позволяет динамически определять нужный алгоритм (то есть можно в рантайме подключать нужный субкласс интерфейса) а фабрика при всем этом в алгоритме имеет метод который возвращает объект.

Паттерн Фабричный метод (Factory Method) - уровень класса

Название и классификация паттерна

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

Назначение

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

Известен также под именем Virtual Constructor (виртуальный конструктор).

Применимость паттерна Factory Method

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

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

Описание паттерна 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 передается соответствующий идентификатор типа.

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

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

Указанные недостатки отсутствуют в классической реализации паттерна 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 является наиболее распространенным, но не единственным. Возможны следующие вариации:

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

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

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

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

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

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

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

Паттерн Фабричный метод часто вызывается внутри Шаблонных методов.

Паттерн Абстрактная фабрика (Abstract Factory) - уровень объекта

Название и классификация паттерна

Абстрактная фабрика - паттерн, порождающий объекты.

Назначение

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

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

Использование паттерна 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.

Результаты

Паттерн Абстрактная фабрика обладает следующими плюсами и минусами:

  • изолирует конкретные классы. Помогает контролировать классы объектов, создаваемых приложением. Поскольку фабрика инкапсулирует ответственность за создание классов и сам процесс их создания, то она изолирует клиента от деталей реализации классов. Клиенты манипулируют экземплярами через их абстрактные интерфейсы. Имена изготавливаемых классов известны только конкретной фабрике, в коде клиента они не упоминаются;
  • упрощает замену семейств продуктов. Класс конкретной фабрики появляется в приложении только один раз: при инстанцировании. Это облегчает замену используемой приложением конкретной фабрики. Приложение может изменить конфигурацию продуктов, просто подставив новую конкретную фабрику. Поскольку абстрактная фабрика создает все семейство продуктов, то и заменяется сразу все семейство. В нашем примере пользовательского интерфейса перейти от виджетов Motif к виджетам Presentation Manager можно, просто переключившись на продукты соответствующей фабрики и заново создав интерфейс;
  • гарантирует сочетаемость продуктов. Если продукты некоторого семейства спроектированы для совместного использования, то важно, чтобы приложение в каждый момент времени работало только с продуктами единственного семейства. Класс Abstract Factory позволяет легко соблюсти это ограничение;
  • поддержать новый вид продуктов трудно. Расширение абстрактной фабрики для изготовления новых видов продуктов - непростая задача. Интерфейс Abstract Factory фиксирует набор продуктов, которые можно создать. Для поддержки новых продуктов необходимо расширить интерфейс фабрики, т. е. изменить класс AbstractFactory и все его подклассы.

Пример кода для паттерна 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).

Описание

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

Шаблон используется в случаях если:

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

Схожие шаблоны и их отличия

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

Реализация шаблона в общем виде

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

Примеры реализации

1. Абстрактный метод или метод из интерфейса

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

Рассмотрим на примере класса 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);

2. Метод класса

Данный подход почти аналогичен рассмотренному выше варианту. Единственное отличие заключается в том, что базовый класс содержит реализации метода CreateStorage() и интерфейса IDocStorage . Потомки могут как использовать их, так и переопределить, если необходимо изменить функциональность.

3. Параметризованный метод

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

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()); } }

4. Использование generics (общих типов/шаблонов)

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

В C# есть хорошая возможность ограничить типы, используемые в качестве параметра generics, используя ключевое слово where. Так, для класса DocumentManagerGeneric будем требовать наличие IDocStorage и public конструктора без параметров.

Теперь создадим generic-класс, унаследовав его от DocumentManager :

Public class DocumentManagerGeneric : DocumentManager where T: IDocStorage, new() { public override IDocStorage CreateStorage() { IDocStorage storage = new T(); // TODO: Setup, test, or do something else with the storage, if required. return storage; } }

При создании экземпляра этого класса, необходимо указать класс используемого хранилища:

DocumentManager docManager = new DocumentManagerGeneric();

В дальнейшем его экземпляр и будет использоваться в методе Save() .

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

Создадим хранилище, требуемого типа, в метода SetStorage() и сохраним его в закрытом поле:

Public class DocumentManager { private IDocStorage _storage; public void SetStorage() where T: IDocStorage, new() { this._storage = new T(); // TODO: Setup, test, or do something else with the storage, if required. } }

Сам тип становится известен только при разработке кода, использующего класс DocumentManager :

DocumentManager docManager2 = new DocumentManager(); docManager2.SetStorage(); docManager2.Save();

Возможно возникнет вопрос, почему просто не передавать хранилище как параметр? Однако, используемый вариант позволяет:

  • вынести в метод SetStorage() не только создание, но и настройку экземпляра класса;
  • выполнить проверку поддержки требуемого интерфейса IDocStorage на этапе компиляции;
  • создать экземпляр класса хранилища только для внутреннего использования.

Таком образом уменьшается зависимость класса DocumentManager от внешнего кода и увеличивается контроль над экземпляром класса хранилища. Например, нет необходимости ожидать, что хранилище может быть закрыто клиентским кодом через свою переменную, указывающую на этот же экземпляр.