Фабричный паттерн. Паттерн Абстрактная фабрика (Abstract Factory) — уровень объекта

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

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

Фабричный метод (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 от внешнего кода и увеличивается контроль над экземпляром класса хранилища. Например, нет необходимости ожидать, что хранилище может быть закрыто клиентским кодом через свою переменную, указывающую на этот же экземпляр.

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). И этого, увы, не избежать. Но подобная проблема присутствует во многих порождающих шаблонах. К достоинствам же следует отнести возможность создавать объекты более универсально, не ориентируясь на конкретные классы и оперируя общим интерфейсом.

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) - уровень класса

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

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

Назначение

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

Известен также под именем 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

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

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

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

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