Паттерн Factory Method (фабричный метод). Паттерн Фабричный метод (Factory Method) — уровень класса Классическая реализация паттерна Factory Method

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

2. Задачи:

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

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

3. Решение:

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

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

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

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

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

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

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

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

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

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

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

    abstract class Product

    public abstract decimal PurchasePrice {get ; set ;}

    public abstract decimal Price {get ; set ;}

    public abstract string Description {get ; set ;}

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

    class Computer: Product

    private decimal _purchase_price;

    private decimal _price;

    private string _description;

    public Computer(string _description, decimal _purchase_price,

    decimal _price)

    this ._description = _description;

    this ._purchase_price = _purchase_price;

    this ._price = _price;

    public override string Description

    get { return _description; }

    set { _description = value; }

    public override decimal Price

    get { return _price; }

    set { _price = value; }

    public override decimal PurchasePrice

    get { return _purchase_price; }

    set { _purchase_price = value; }

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

    abstract class Creator

    public abstract Product FactoryMethod(string _description,

    decimal _purchase_price, decimal _price);

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

    class ComputerCreator: Creator

    public override Product FactoryMethod(string _description,

    decimal _purchase_price, decimal _price)

    return new Computer(_description,_purchase_price,_price);

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

    static void Main(string args)

    ListProductList = new List

    Creator creators = new Creator;

    creators = new ComputerCreator();

    foreach (Creator cr in creators)

    if (cr is ComputerCreator)

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

    foreach (Product pr in productList)

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

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

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

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

    pr.GetType().Name,

  1. pr.PurchasePrice,

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

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

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

Последнее обновление: 31.10.2015

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

Когда надо применять паттерн

    Когда заранее неизвестно, объекты каких типов необходимо создавать

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

    Когда создание новых объектов необходимо делегировать из базового класса классам наследникам

На языке UML паттерн можно описать следующим образом:

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

Abstract class Product {} class ConcreteProductA: Product {} class ConcreteProductB: Product {} abstract class Creator { public abstract Product FactoryMethod(); } class ConcreteCreatorA: Creator { public override Product FactoryMethod() { return new ConcreteProductA(); } } class ConcreteCreatorB: Creator { public override Product FactoryMethod() { return new ConcreteProductB(); } }

Участники

    Абстрактный класс Product определяет интерфейс класса, объекты которого надо создавать.

    Конкретные классы ConcreteProductA и ConcreteProductB представляют реализацию класса Product. Таких классов может быть множество

    Абстрактный класс Creator определяет абстрактный фабричный метод FactoryMethod() , который возвращает объект Product.

    Конкретные классы ConcreteCreatorA и ConcreteCreatorB - наследники класса Creator, определяющие свою реализацию метода FactoryMethod() . Причем метод FactoryMethod() каждого отдельного класса-создателя возвращает определенный конкретный тип продукта. Для каждого конкретного класса продукта определяется свой конкретный класс создателя.

    Таким образом, класс Creator делегирует создание объекта Product своим наследникам. А классы ConcreteCreatorA и ConcreteCreatorB могут самостоятельно выбирать какой конкретный тип продукта им создавать.

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

Class Program { static void Main(string args) { Developer dev = new PanelDeveloper("ООО КирпичСтрой"); House house2 = dev.Create(); dev = new WoodDeveloper("Частный застройщик"); House house = dev.Create(); Console.ReadLine(); } } // абстрактный класс строительной компании abstract class Developer { public string Name { get; set; } public Developer (string n) { Name = n; } // фабричный метод abstract public House Create(); } // строит панельные дома class PanelDeveloper: Developer { public PanelDeveloper(string n) : base(n) { } public override House Create() { return new PanelHouse(); } } // строит деревянные дома class WoodDeveloper: Developer { public WoodDeveloper(string n) : base(n) { } public override House Create() { return new WoodHouse(); } } abstract class House { } class PanelHouse: House { public PanelHouse() { Console.WriteLine("Панельный дом построен"); } } class WoodHouse: House { public WoodHouse() { Console.WriteLine("Деревянный дом построен"); } }

В качестве абстрактного класса Product здесь выступает класс House. Его две конкретные реализации - PanelHouse и WoodHouse представляют типы домов, которые будут строить подрядчики. В качестве абстрактного класса создателя выступает Developer, определяющий абстрактный метод Create() . Этот метод реализуется в классах-наследниках WoodDeveloper и PanelDeveloper. И если в будущем нам потребуется построить дома какого-то другого типа, например, кирпичные, то мы можем с легкостью создать новый класс кирпичных домов, унаследованный от House, и определить класс соответствующего подрядчика. Таким образом, система получится легко расширяемой. Правда, недостатки паттерна тоже очевидны - для каждого нового продукта необходимо создавать свой класс создателя.

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

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

Проблема

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

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


Добавить новый класс не так-то просто, если весь код уже завязан на конкретные классы.

Отличные новости, правда?! Но как насчёт кода? Большая часть существующего кода жёстко привязана к классам Грузовиков. Чтобы добавить в программу классы морских Судов, понадобится перелопатить всю программу. Более того, если вы потом решите добавить в программу ещё один вид транспорта, то всю эту работу придётся повторить.

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

Решение

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


Подклассы могут изменять класс создаваемых объектов.

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

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


Все объекты-продукты должны иметь общий интерфейс.

Например, классы Грузовик и Судно реализуют интерфейс Транспорт с методом доставить. Каждый из этих классов реализует метод по-своему: грузовики везут грузы по земле, а суда - по морю. Фабричный метод в классе ДорожнойЛогистики вернёт объект-грузовик, а класс МорскойЛогистики - объект-судно.


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

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

Структура



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

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

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

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

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

    Конкретные создатели по-своему реализуют фабричный метод, производя те или иные конкретные продукты.

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

Псевдокод

В этом примере Фабричный метод помогает создавать кросс-платформенные элементы интерфейса, не привязывая основной код программы к конкретным классам элементов.


Пример кросс-платформенного диалога.

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

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

Такой подход можно применить и для создания других элементов интерфейса. Хотя каждый новый тип элементов будет приближать вас к Абстрактной фабрике .

// Паттерн Фабричный метод применим тогда, когда в программе // есть иерархия классов продуктов. interface Button is method render() method onClick(f) class WindowsButton implements Button is method render(a, b) is // Отрисовать кнопку в стиле Windows. method onClick(f) is // Навесить на кнопку обработчик событий Windows. class HTMLButton implements Button is method render(a, b) is // Вернуть HTML-код кнопки. method onClick(f) is // Навесить на кнопку обработчик события браузера. // Базовый класс фабрики. Заметьте, что "фабрика" - это всего // лишь дополнительная роль для класса. Скорее всего, он уже // имеет какую-то бизнес-логику, в которой требуется создание // разнообразных продуктов. class Dialog is method render() is // Чтобы использовать фабричный метод, вы должны // убедиться в том, что эта бизнес-логика не зависит от // конкретных классов продуктов. Button - это общий // интерфейс кнопок, поэтому все хорошо. Button okButton = createButton() okButton.onClick(closeDialog) okButton.render() // Мы выносим весь код создания продуктов в особый метод, // который назвают "фабричным". abstract method createButton() // Конкретные фабрики переопределяют фабричный метод и // возвращают из него собственные продукты. class WindowsDialog extends Dialog is method createButton() is return new WindowsButton() class WebDialog extends Dialog is method createButton() is return new HTMLButton() class Application is field dialog: Dialog // Приложение создаёт определённую фабрику в зависимости от // конфигурации или окружения. method initialize() is config = readApplicationConfigFile() if (config.OS == "Windows") then dialog = new WindowsDialog() else if (config.OS == "Web") then dialog = new WebDialog() else throw new Exception("Error! Unknown operating system.") // Если весь остальной клиентский код работает с фабриками и // продуктами только через общий интерфейс, то для него // будет не важно, какая фабрика была создана изначально. method main() is this.initialize() dialog.render()

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

Когда заранее неизвестны типы и зависимости объектов, с которыми должен работать ваш код.

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

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

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

Пользователи могут расширять классы вашего фреймворка через наследование. Но как сделать так, чтобы фреймворк создавал объекты из этих новых классов, а не из стандартных?

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

Например, вы используете готовый UI-фреймворк для своего приложения. Но вот беда - требуется иметь круглые кнопки, вместо стандартных прямоугольных. Вы создаёте класс RoundButton . Но как сказать главному классу фреймворка UIFramework , чтобы он теперь создавал круглые кнопки, вместо стандартных?

Для этого вы создаёте подкласс UIWithRoundButtons из базового класса фреймворка, переопределяете в нём метод создания кнопки (а-ля createButton) и вписываете туда создание своего класса кнопок. Затем используете UIWithRoundButtons вместо стандартного UIFramework .

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

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

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

  1. Сначала вам следует создать общее хранилище, чтобы хранить в нём все создаваемые объекты.
  2. При запросе нового объекта нужно будет заглянуть в хранилище и проверить, есть ли там неиспользуемый объект.
  3. А затем вернуть его клиентскому коду.
  4. Но если свободных объектов нет - создать новый, не забыв добавить его в хранилище.

Весь этот код нужно куда-то поместить, чтобы не засорять клиентский код.

Самым удобным местом был бы конструктор объекта, ведь все эти проверки нужны только при создании объектов. Но, увы, конструктор всегда создаёт новые объекты, он не может вернуть существующий экземпляр.

Значит, нужен другой метод, который бы отдавал как существующие, так и новые объекты. Им и станет фабричный метод.

Шаги реализации

    Приведите все создаваемые продукты к общему интерфейсу.

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

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

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

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

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

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

    Например, у вас есть класс Почта с подклассами АвиаПочта и НаземнаяПочта, а также классы продуктов Самолёт, Грузовик и Поезд. Авиа соответствует Самолётам, но для НаземнойПочты есть сразу два продукта. Вы могли бы создать новый подкласс почты для поездов, но проблему можно решить и по-другому. Клиентский код может передавать в фабричный метод НаземнойПочты аргумент, контролирующий тип создаваемого продукта.

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

    Фабричный метод , наоборот, построен на наследовании, но не требует сложной инициализации.

    Фабричный метод можно рассматривать как частный случай Шаблонного метода . Кроме того, Фабричный метод нередко бывает частью большого класса с Шаблонными методами .

    Паттерн Абстрактная фабрика (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 часто реализуются фабричными методами (паттерн Фабричный метод), но могут быть реализованы и с помощью паттерна Прототип. Конкретная фабрика может быть описана паттерном Одиночка.