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

Немного о порождающих шаблонах проектирования

Тема шаблонов проектирования достаточно популярна. По ней снято много роликов и написано статей. Объединяет все эти материалы «анти-паттерн» Ненужная сложность (Accidental complexity). В результате примеры заумные, описание запутанное, как применять не понятно. Да и главная задача шаблонов проектирования – упрощение (кода, и работы в целом) не достигается. Ведь применение шаблона требует дополнительных усилий. Примерно так же, как и Unit тестирование.

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

К порождающим шаблонам можно отнести шесть:

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

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

На этот вопрос отвечают три шаблона: «Прототип», «Абстрактная фабрика» и «Фабричный метод».

В рамках концепции ООП есть только три места где теоретически можно породить новый экземпляр (instance):

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

фабричный метод зачем нужен. Смотреть фото фабричный метод зачем нужен. Смотреть картинку фабричный метод зачем нужен. Картинка про фабричный метод зачем нужен. Фото фабричный метод зачем нужен

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

«Прототип»

Данный шаблон соответствует месту «Продукт», по сути является конструктором класса. Соответственно, всегда порождается экземпляр конкретного (заранее известного) класса.
В рамках данного шаблона конструктор знает только непосредственно переданные ему параметры (количество параметров стремится к числу полей класса). Разумеется, есть полный доступ ко всем полям и свойствам создаваемого класса.

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

Что нам дает этот шаблон:

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

«Абстрактная фабрика»

Некий класс Партнер. Может быть специализированным, либо «совмещать». Может быть статическим (без экземпляра). Примером «совместительства» может быть класс настроек (configuration). Также может быть скрыта за «Фасадом» (Facade).

«Фабрика» как правило видит все глобальные настройки приложения (либо отдельной подсистемы). Непосредственное порождение может быть делегировано «Прототипу». При этом количество входящих параметров в методе Фабрики будет меньше, чем в аналогичном конструкторе Прототипа. Фабрика не принимает решение «кого создавать» на основании входящих параметров.

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

Очень популярен. Этот шаблон используют те, кто прослушал курс GoF. Как правило код становится еще хуже, чем «до применения шаблонов».

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

В ряде случаев удобно прятать Фабрики за Фасадом. Например, в приложении есть десяток своих фабрик, и десяток из библиотек. Для них можно построить Фасад. Это позволит не линковать библиотеки к каждому модулю, а также легко подменять одну фабрику на другую.

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

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

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

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

Минусов по сути нет.

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

Маленький пример

У нас есть класс для протоколирования в файл на жестком диске. Вот так могут выглядеть порождающие методы в рамках шаблонов «Где?»:

Прототип:

Все что должен знать конструктор передается ему в виде параметров.

Фабрика:

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

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

В классе Клиенте, известно с какой деталировкой производить протоколирование.

В данной конструкции для подмены класса протоколирования на заглушку достаточно переопределить NewLogger в потомке Клиента. Это полезно при проведении Unit тестов.

Что бы производить протоколирование в базу данных, достаточно переопределить метод GetLogger в потомке Фабрики.

Источник

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

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

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

фабричный метод зачем нужен. Смотреть фото фабричный метод зачем нужен. Смотреть картинку фабричный метод зачем нужен. Картинка про фабричный метод зачем нужен. Фото фабричный метод зачем нужен

Проблема

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

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

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

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

Решение

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

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

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

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

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

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

Структура

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

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

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

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

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

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

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

Псевдокод

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

фабричный метод зачем нужен. Смотреть фото фабричный метод зачем нужен. Смотреть картинку фабричный метод зачем нужен. Картинка про фабричный метод зачем нужен. Фото фабричный метод зачем нужен Пример кросс-платформенного диалога.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Достоинства и недостатки

Достоинства:

Недостатки:

Источник

Как использовать фабричный метод при написании кода на Python

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

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

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

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

Вместо использования сложной структуры из условий if/elif/else для определения реализации, приложение делегирует это решение отдельному компоненту, который создает конкретный объект. При таком подходе код приложения упрощается, становится более удобным для повторного использования и поддержки.

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

В приведенном выше примере есть базовый класс Song для представления песни и класс SongSerializer, который преобразовывает объект Song в его строковое представление в соответствии со значением параметра format.

Метод .serialize() поддерживает два разных формата: JSON и XML. Любой другой указанный формат не поддерживается, поэтому возникает исключение ValueError.

Воспользуемся интерактивной оболочкой Python, чтобы увидеть, как работает код:

Создаются объект Song и serializer, затем Song преобразуется в строковое представление с помощью метода .serialize(). Метод принимает в качестве параметров объект Song и строковое значение. Последний вызов использует YAML в качестве формата, который не поддерживает serializer, поэтому возникает исключение ValueError.

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

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

фабричный метод зачем нужен. Смотреть фото фабричный метод зачем нужен. Смотреть картинку фабричный метод зачем нужен. Картинка про фабричный метод зачем нужен. Фото фабричный метод зачем нужен

Пример выше раскрывает проблемы, с которыми вы столкнетесь в сложном логическом коде. Структуры if/elif/else используются для изменения поведения приложения, но они усложняют чтение, восприятие и поддержку.

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

Метод .serialize() в SongSerializer может изменяться по многим причинам. Такое поведение способно привести к появлению проблем. Давайте рассмотрим все возможные ситуации, когда придется вносить изменения в реализацию:

В идеале, любое изменение вносится без использования метода .serialize().

В поисках общего интерфейса

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

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

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

В примере выше мы сначала осуществляем сериализацию в JSON и XML, а затем предоставляем отдельный компонент, который решает, какую реализацию использовать на основе указанного формата. Этот компонент оценивает значение format и возвращает конкретную реализацию, определенную его значением.

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

Рефакторинг кода в желаемый интерфейс

Желаемый интерфейс − это объект или функция, которая принимает объект Song и возвращает строковое представление.

Первым шагом является рефакторинг одного из логических ответвлений в этот интерфейс. Добавляем новый метод ._serialize_to_json() и перемещаем в него код сериализации JSON. Затем изменяем клиент для вызова, вместо реализации в теле оператора if :

После внесения этих изменений вы сможете убедиться, что поведение осталось прежним. Затем делаем то же самое для XML, представляя новый метод ._serialize_to_xml(), перемещая реализацию в него и изменяя ветку elif для вызова.

В следующем примере показан переработанный код:

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

Базовая реализация фабричного метода

Главная идея фабричного метода заключается в том, чтобы предоставить отдельному компоненту ответственность за решение, какую реализацию следует использовать на основе определенного параметра. Этим параметром в нашем примере является format.

Чтобы завершить реализацию фабричного метода, вы добавляете новый метод ._get_serializer(), который принимает желаемый формат. Он оценивает значение format и возвращает соответствующую функцию сериализации:

Теперь можно изменить метод .serialize() в SongSerializer для использования ._get_serializer(), чтобы завершить реализацию фабричного метода. Вот так:

Окончательная реализация показывает различные компоненты фабричного метода.

Это − клиентский компонент шаблона. Определенный интерфейс называется компонентом-продуктом, в нашем случае продукт − это функция, которая принимает Song и возвращает строковое представление.

фабричный метод зачем нужен. Смотреть фото фабричный метод зачем нужен. Смотреть картинку фабричный метод зачем нужен. Картинка про фабричный метод зачем нужен. Фото фабричный метод зачем нужен

Методы ._serialize_to_json() и ._serialize_to_xml() являются конкретными реализациями продукта.

Наконец, метод ._get_serializer() является компонентом-создателем. Создатель решает, какую реализацию использовать.

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

Обычно это не так, как видно, ни один из добавленных методов не использует параметр self. Это признак того, что они не должны быть методами класса SongSerializer, а могут стать внешними функциями:

Механика фабричного метода всегда одинакова. Клиент зависит от конкретной реализации интерфейса. Он запрашивает реализацию от компонента-создателя, используя какой-то идентификатор.

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

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

Вы создаете Song и serializer и используете serializer для преобразования Song в строковое представление с указанием формата. Поскольку YAML не является поддерживаемым форматом, появляется ValueError.

Предпосылки для использования фабричного метода

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

Существует широкий спектр похожих задач, поэтому давайте рассмотрим примеры.

Замена сложного логического кода: сложные логические структуры if/elif/else трудно поддерживать, поскольку при изменении требований необходимы новые логические ветки.

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

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

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

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

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

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

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

Заключение

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

Что еще почитать

Понравился материал о том, как применять фабричный метод? Другие материалы по теме:

Источник: Фабричный метод и как его применять при разработке на Python on Real Python

Источник

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

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