Интернет. Программы. Windows. Операционные системы. Игры. Восстановление

Фабричный метод на Java. Factory Method (Фабричный метод) Паттерн фабричный метод c

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

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

Назначение

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

Псевдоним

Паттерн Фабричный метод известен также под именем 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 для для инициализации объекта. Фабричному
методу такая операция не требуется.

Описание Factory Method

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

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

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

Структура

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

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

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

Пример

Предположим мы создаем некий XML парсер, который анализирует предоставленный файл и преобразует его в DOM дерево. Каждый элемент этого дерева назовем нодой (Node). В время разбора файла, перед нами встанет задача порождения новых нод, и мы напишем там примерно такой код:

Class Xml_Node() { /*...*/ public function parse() { /*...*/ $ChildNode = new Xml_Node(); /*...*/ } /*...*/ }

Что в этом плохого? Приведу такой пример: мы захотим на основе XML файла строить структуру объектов, определенного класса, чтобы использовать ее в дальнейшем, и нам, в соответствии с принципом "до тебя уже все написано", захотелось использовать готовый класс XML_Node .

Мы делаем своего наследника XML_Node_Processor , и хотим теперь повлиять на процесс анализа файла так, чтобы при определенном теге инстанцировался определенный класс (Для тега food - My_Food , для cow - My_Big_Orange_Cow). И при реализации как приведена выше, для этого нам придется полностью перегрузить метод parse, ради того, чтобы сделать копипаст кода из родительского класса отредактировав всего одну строку кода. Согласитесь, это глупо.

Суть паттерна

Возможная реализация на PHP

Abstract class XML_Node_Abstract { abstract function createNode($tag); } class Xml_Node extends XML_Node_Abstract { /*...*/ public function createNode($tag) { return new Xml_Node(); } /*...*/ public function parse() { /*...*/ $ChildNode = $this -> createNode($Tag); /*..*/ } } class Xml_Node_Processor extends Xml_Node { public function createNode($tag) { switch($tag) { case "food": return new My_Food(); case "cow": return new My_Big_Orange_Cow(); } return parent::createNode($tag); } } class My_Food extends Xml_Node_Processor {}; class My_Big_Orange_Cow extends Xml_Node_Processor {};

В заключение

  • В реализации фабричного метода не всегда нужен абстрактный класс создателя (XML_Node_Abstract). На его месте может использоваться конкретный экземпляр. Из этого примера можно выкинуть XML_Node_Abstract и ничего не изменится
  • Результат возвращаемый фабричным методом, должен всегда соответствовать заданному интерфейсу (в нашем случае интерфейсу класса Xml_Node)
  • Фабричный метод может быть статической функцией, и использоваться для инстанации объектов подкласса
  • Фабричный метод не обязательно должен возвращать объект, он так же может возвращать класс. При этом все наследники и родители так же должны возвращать класс.

Фактически состоит из фабричных методов

Дополнено

Вопрос

Не понял. Смысл в том, чтобы в методе parse наследников создавались экземпляры именно их, а не родителя?

Почему бы вместо:

$ChildNode = new Xml_Node () ;

не сделать:

$ChildNode = new static; ?

Ответ

new static не решает проблему, решение которой возложено на фабричный метод. Его основная задача убрать зависимость из кода, зависимость от конкретного класса. Казалось бы, что плохого в этом? Ничего. Ровно до той поры, пока не потребуется расширить класс, внести некоторую логику или наладить модульное тестирование.

Вот представьте, у вас есть такой код в нескольких местах:

$node = new Xml_Node (); $title = $node->getTitle ();

Приходит к вам проект менеджер и говорит, что xml будут приходить в двух разных форматах. Подумаешь тоже:

If ($this -> isFormatOne ()) { $node = new Xml_Node (); } else { $node = new Xml_Node_Extended (); } $title = $node -> getTitle ();

Затем он приходит снова, и говорит, что форматов теперь будет 3,10,500. При такой архитектуре, придется КАЖДЫЙ раз вносить изменения во ВСЕ вхождения такого кода. Если же использовать фабричный метод, то придется изменить только его, а создания объекта будет выглядеть всегда одинково:

$node = $this -> createNode (); $title = $node -> getTitle ();

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

Вам также будет интересно:

Язык Visual Basic — примеры написания кода
Функция возвращает одномерный массив, содержащий определенное количество подстрок. Данная...
Не зарегистрирован в сети - решение
Многие пользователи сталкиваются с проблемой, когда телефон либо планшет на базе Android...
Удержание предметов в Steam
Для защиты аккаунта, все продаваемые или обмениваемые вами предметы будут удержаны....
Обзор Sony Tablet Z: планшет, который мы ждали
Влагозащищённый, мощный, красочный планшет Xperia™ Tablet Z с HD дисплеем и процессором...
Ремонт сотовых телефонов Meizu
Сервисный центр по ремонту телефонов Meizu: замена стекла, экрана, тачскрина, разъема...