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




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

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

Назначение

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

Псевдоним

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

Мотивация

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

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

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

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

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

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

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

Структура

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

Участники

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

Отношения

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

Результаты

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

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

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

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

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

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

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

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

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

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

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

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

Вот схема:

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

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

Реализация

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

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

Пример кода

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

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

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

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

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

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

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

Проблема

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

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


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

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

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

Решение

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Описание Factory Method

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

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

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

    Структура

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