Зачем нужен фабричный метод
Перейти к содержимому

Зачем нужен фабричный метод

  • автор:

Фабричный метод

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

Паттерн Фабричный метод

Проблема

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

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

Проблема с добавлением нового класса в программу

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

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

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

Решение

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

Структура классов-создателей

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

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

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

Структура иерархии продуктов

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

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

Программа после применения фабричного метода

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

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

Структура

Структура классов паттерна Фабричный метод Структура классов паттерна Фабричный метод

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

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

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

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

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

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

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

Псевдокод

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

Структура классов примера паттерна Фабричного метода

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Преимущества и недостатки

  • Избавляет класс от привязки к конкретным классам продуктов.
  • Выделяет код производства продуктов в одно место, упрощая поддержку кода.
  • Упрощает добавление новых продуктов в программу.
  • Реализует принцип открытости/закрытости.
  • Может привести к созданию больших параллельных иерархий классов, так как для каждого класса продукта надо создать свой подкласс создателя.

Отношения с другими паттернами

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

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

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

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

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

Примеры реализации паттерна

Дополнительные материалы

  • Если вы уже слышали о Фабрике, Фабричном методе или Абстрактной фабрике, но с трудом их различаете — почитайте нашу статью Сравнение фабрик.

Не втыкай в транспорте

Лучше почитай нашу книгу о паттернах проектирования.

Теперь это удобно делать даже во время поездок в общественном транспорте.

Эта статья является частью нашей электронной книги Погружение в Паттерны Проектирования.

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

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

Фабричный метод

Проблема

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

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

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

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

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

Решение

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

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

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

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

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

Например, классы Truck (грузовик) и Ship (судно) реализуют интерфейс Transport (транспорт) с методом deliver() (доставить). Каждый из этих классов реализует этот метод по-своему: грузовики везут грузы по земле, а суда – по морю. Фабричный метод в классе RoadLogistic (дорожная логистика) возвращает объекты-грузовики, а класс SeaLogistics (морская логистика) – объекты-суда.

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

Код, использующий фабричный метод (часто называемый клиентским кодом), не видит разницы между фактическими продуктами, возвращаемыми различными подклассами. Клиент рассматривает все продукты как объекты абстрактного класса Transport (транспорт). Клиент знает, что все объекты Transport должны иметь метод deliver() , но то, как именно он работает, для клиента не имеет значения.

Структура

Структура классов паттерна Фабричный метод Структура классов паттерна Фабричный метод

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

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

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

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

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

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

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

Псевдокод

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

Структура классов примера паттерна Фабричного метода Пример кросс-платформенного диалога.

Базовый класс диалога использует различные элементы пользовательского интерфейса для визуализации своего окна. В разных операционных системах эти элементы могут выглядеть немного по-разному, но они всё равно должны вести себя одинаково. Кнопка в Windows остается кнопкой и в Linux.

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

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

Конечно, вы можете применить этот подход и к другим элементам пользовательского интерфейса. Однако с каждым новым фабричным методом, который вы добавляете в диалог, вы приближаетесь к паттерну «Абстрактная фабрика». Мы поговорим о нем позже.

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

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

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

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

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

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

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

Рассмотрим, как это будет работать. Представьте, что вы пишете приложение, используя фреймворк пользовательского интерфейса с открытым исходным кодом. В вашем приложении должны быть круглые кнопки, но во фреймворке есть только квадратные. Вы расширяете стандартный класс Button великолепным подклассом RoundButton . Но теперь вам нужно указать основному классу UIFramework использовать этот новый подкласс кнопки вместо подкласса по умолчанию.

Для этого вы создаете подкласс UIWithRoundButtons из базового класса фреймворка и переопределяете его метод createButton . Хотя этот метод в базовом классе возвращает объекты Button , вы заставляете свой подкласс возвращать объекты RoundButton . Теперь используйте класс UIWithRoundButtons вместо UIFramework . Вот и всё!

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

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

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

  1. во-первых, вам нужно создать хранилище, чтобы отслеживать все созданные объекты;
  2. когда кто-то запрашивает объект, программа должна поискать свободный объект внутри этого пула;
  3. … а затем вернуть его клиентскому коду;
  4. если свободных объектов нет, программа должна создать новый (и добавить его в пул).

Это много кода! И всё это должно быть собрано в одном месте, чтобы вы не засоряли программу дублированием кода.

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

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

Как реализовать

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

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

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

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

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

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

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

Например, представьте, что у вас есть следующая иерархия классов: базовый класс Mail (почта) с парой подклассов: AirMail (авиапочта) и GroundMail (наземная почта); класс Transport (транспорт) с подклассами Plane (самолет), Truck (грузовик) и Train (поезд). В то время как класс AirMail использует только объекты Plane , GroundMail может работать как с объектами Truck , так и с объектами Train . Вы можете создать новый подкласс (скажем, TrainMail ) для обработки обоих случаев, но есть и другой вариант. Клиентский код может передать аргумент фабричному методу класса GroundMail , чтобы контролировать, какой продукт он хочет получить.

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

Шаблоны проектирования

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

Класс Burger , абстрактный класс. Глядя на него мы можем понять, что должен собой представлять бургер. И что все бургеры должны быть похожи на него. Создадим два наследуемых класса от Burger с некоторыми особенностями. Получили Hamburger и Cheeseburger . На нашей кухне есть повар( new Chef() ) реализует интерфейс FactoryMethodInterface , значит, он умеет делать бургеры. Каждый повар может создавать бургеры по-разному, но на выходе мы всегда получим Burger . Что бы повар приготовил нам бургер мы вызываем его метод ( Chef::makeBurger() ) и передаём название бургера. Повар создает нужный нам бургер или говорит что такой бургер он готовить не умеет.

Name already in use

Developer-Knowledge-Base / design_patterns / creational / factory_method.md

  • Go to file T
  • Go to line L
  • Copy path
  • Copy permalink
  • Open with Desktop
  • View raw
  • Copy raw contents Copy raw contents

Copy raw contents

Copy raw contents

Фабричный метод (Factory method)

Также известен как Виртуальный конструктор (Virtual constructor).

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *