Фабрика (объектно-ориентированное программирование) - Factory (object-oriented programming)

Заводской метод в LePUS3

В объектно-ориентированное программирование (ООП), а фабрика является объект за создание других объектов - формально фабрика - это функция или метод, возвращающий объекты разного прототипа или класса[1] от вызова некоторого метода, который считается «новым».[а] В более широком смысле подпрограмма который возвращает «новый» объект, может называться «фабрикой», как в заводской метод или же заводская функция. Это основная концепция ООП, которая служит основой для ряда связанных шаблоны проектирования программного обеспечения.

Мотивация

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

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

Терминология

Терминология отличается относительно того, является ли сама концепция фабрики шаблоном проектирования - в основополагающей книге Шаблоны проектирования нет "заводского шаблона", а вместо него два шаблона (шаблон заводского метода и абстрактный завод шаблон ), которые используют фабрики. Некоторые источники называют это понятие заводской образец,[2][3] в то время как другие считают саму концепцию идиома программирования,[4] использование терминов «фабричный паттерн» или «фабричный паттерн» для более сложных паттернов, использующих фабрики, чаще всего паттерна фабричного метода; в этом контексте сама концепция фабрики может быть названа простая фабрика.[4] В других контекстах, особенно в языке Python, используется сама «фабрика», как в этой статье.[5] В более широком смысле "фабрика" может применяться не только к объекту, который возвращает объекты из вызова некоторого метода, но и к объекту подпрограмма который возвращает объекты, как в заводская функция (даже если функции не являются объектами) или заводской метод.[6] Поскольку во многих языках фабрики вызываются путем вызова метода, общее понятие фабрики часто путают с конкретным. шаблон заводского метода шаблон дизайна.

Использовать

ООП обеспечивает полиморфизм на объекте использовать к метод отправки, формально полиморфизм подтипа через разовая отправка определяется типом объекта, для которого вызывается метод. Однако это не работает для конструкторов, поскольку конструкторы Создайте объект некоторого типа, а не использовать существующий объект. Более конкретно, когда вызывается конструктор, еще нет объекта для отправки.[b]

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

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

Например, в Python collections.defaultdict учебный класс[7] имеет конструктор, который создает объект типа defaultdict[d] значения по умолчанию получаются при вызове фабрики. Фабрика передается в качестве аргумента конструктору и сама может быть конструктором или любым предметом, который ведет себя как конструктор - вызываемым объектом, который возвращает объект, то есть фабрику. Например, используя список конструктор для списков:

# collections.defaultdict ([default_factory [, ...]])d = defaultdict(список)

Создание объекта

Фабричные объекты используются в ситуациях, когда получение объекта определенного типа является более сложным процессом, чем простое создание нового объекта, особенно если требуется сложное выделение или инициализация. Некоторые из процессов, необходимых для создания объекта, включают определение того, какой объект создать, управление временем жизни объекта и управление специализированными задачами создания и удаления объекта. Заводской объект может решить создать объект учебный класс (если применимо) динамически, вернуть его из пул объектов, выполнить сложную настройку объекта или другие вещи. Аналогично, используя это определение, a одиночка осуществляется одноэлементный образец является формальной фабрикой - она ​​возвращает объект, но не создает новых объектов за пределами единственного экземпляра.

Примеры

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

def ж():    возвращаться А()

Простая фабричная функция, реализующая шаблон singleton:

def ж():    если ж.объект является Никто:        ж.объект = А()    возвращаться ж.объектж.объект = Никто

Это создаст объект при первом вызове и всегда будет возвращать тот же объект после этого.

Синтаксис

Фабрики могут быть вызваны различными способами, чаще всего вызовом метода ( заводской метод), иногда вызывая ее как функцию, если фабрика является вызываемым объектом ( заводская функция). В некоторых языках конструкторы и фабрики имеют идентичный синтаксис, в то время как в других конструкторы имеют особый синтаксис. В языках, где конструкторы и фабрики имеют идентичный синтаксис, например Python, Perl, Ruby, Object Pascal и F #,[e] конструкторы могут быть заменены фабриками. В языках, где они различаются, их нужно различать по интерфейсам, а переключение между конструкторами и фабриками требует изменения вызовов.

Семантика

На языках, где объекты динамически распределяется, как и в Java или Python, фабрики семантически эквивалентны конструкторам. Однако в таких языках, как C ++, которые позволяют статически выделять некоторые объекты, фабрики отличаются от конструкторов для статически распределенных классов, поскольку последние могут иметь распределение памяти, определяемое во время компиляции, в то время как распределение возвращаемых значений фабрик должно определяться в время выполнения. Если конструктор может быть передан в качестве аргумента функции, то вызов конструктора и выделение возвращаемого значения должны выполняться динамически во время выполнения и, таким образом, иметь аналогичную или идентичную семантику для вызова фабрики.

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

Фабрики используются в различных шаблоны проектирования особенно в творческие шаблоны такой как Библиотека объектов шаблонов проектирования. Были разработаны специальные рецепты для их реализации на многих языках. Например, несколько "Шаблоны GoF ", словно "Шаблон заводского метода ","Строитель "или даже"Синглтон "являются реализациями этой концепции.Абстрактный заводской образец "вместо этого - метод построения коллекций фабрик.

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

Приложения

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

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

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

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

Фабрики можно использовать, когда:

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

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

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

Заводские методы используются в разработка через тестирование чтобы классы могли быть подвергнуты тестированию.[9] Если такой класс Фу создает другой объект Опасный что нельзя автоматизировать модульные тесты (возможно, он взаимодействует с производственной базой данных, которая не всегда доступна), затем создание Опасный объекты помещаются в виртуальный заводской метод createDangerous в классе Фу. Для тестирования, TestFoo (подкласс Фу) затем создается с помощью метода виртуальной фабрики createDangerous переопределено для создания и возврата FakeDangerous, а поддельный объект. Затем модульные тесты используют TestFoo проверить функциональность Фу без побочного эффекта от использования настоящего Опасный объект.

Преимущества и варианты

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

Описательные имена

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

    общественный учебный класс Сложный    {        общественный двойной настоящий;        общественный двойной воображаемый;        общественный статический Сложный Откартезианского(двойной настоящий, двойной воображаемый)        {            возвращаться новый Сложный(настоящий, воображаемый);        }        общественный статический Сложный FromPolar(двойной модуль, двойной угол)        {            возвращаться новый Сложный(модуль * Математика.Cos(угол), модуль * Математика.Грех(угол));        }        частный Сложный(двойной настоящий, двойной воображаемый)        {            это.настоящий = настоящий;            это.воображаемый = воображаемый;        }    }Сложный товар = Сложный.FromPolar(1, Математика.ЧИСЛО ПИ);

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

Инкапсуляция

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

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

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

Ява

общественный учебный класс ImageReaderFactory {    общественный статический ImageReader createImageReader(ImageInputStreamProcessor iisp) {        если (iisp.isGIF()) {            возвращаться новый GifReader(iisp.getInputStream());        }        еще если (iisp.isJPEG()) {            возвращаться новый JpegReader(iisp.getInputStream());        }        еще {            бросать новый IllegalArgumentException(«Неизвестный тип изображения».);        }    }}

PHP

учебный класс Фабрика{    общественный статический функция строить($ type)    {        $ класс = "Формат" . $ type;        если (!class_exists($ класс)) {            бросать новый Исключение(«Отсутствует класс формата».);        }        возвращаться новый $ класс;    }}интерфейс ФорматИнтерфейс {}учебный класс FormatString орудия ФорматИнтерфейс {}учебный класс FormatNumber орудия ФорматИнтерфейс {}пытаться {    $ строка = Фабрика::строить("Нить");} ловить (Исключение $ e) {    эхо $ e->getMessage();}пытаться {    число $ = Фабрика::строить("Число");} ловить (Исключение $ e) {    эхо $ e->getMessage();}

Ограничения

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

  • Первое ограничение заключается в том, что рефакторинг существующего класса для использования фабрик нарушает работу существующих клиентов. Например, если класс Complex был стандартным классом, у него могло бы быть множество клиентов с таким кодом, как:
    Сложный c = новый Сложный(-1, 0);
Как только мы понимаем, что необходимы две разные фабрики, мы меняем класс (на показанный код ранее ). Но поскольку конструктор теперь частный, существующий клиентский код больше не компилируется.
  • Второе ограничение заключается в том, что, поскольку в шаблоне используется частный конструктор, класс не может быть расширен. Любой подкласс должен вызывать унаследованный конструктор, но этого нельзя сделать, если этот конструктор является частным.
  • Третье ограничение заключается в том, что если класс должен быть расширен (например, путем защиты конструктора - это рискованно, но выполнимо), подкласс должен обеспечивать собственную повторную реализацию всех фабричных методов с точно такими же сигнатурами. Например, если класс Странный комплекс расширяет Сложный, то если Странный комплекс предоставляет свою версию всех фабричных методов, вызов
    Странный комплекс.FromPolar(1, Математика.число Пи);
    даст экземпляр Сложный (суперкласс), а не ожидаемый экземпляр подкласса. Функции отражения некоторых языков позволяют избежать этой проблемы.

Все три проблемы можно решить, изменив базовый язык программирования, чтобы фабрики стали первоклассными членами класса (см. Также Виртуальный класс ).[10]

Примечания

  1. ^ С точки зрения интерфейса любой объект, возвращающий объект, может использоваться как фабрика, но семантически фабрика возвращает либо вновь созданный объект, например экземпляр класса или копию прототипа, либо объект, который выглядит новым, например, повторно инициализированный объект из пула объектов.
  2. ^ В языках, где конструкторы сами являются методами объекта класса (методы класса ), существует существующий объект, а конструкторы являются частными случаями фабричных методов, а полиморфное создание является частным случаем отправки полиморфных методов. В других языках существует четкое различие между конструкторами и методами.
  3. ^ Конструкторы можно использовать везде, где могут фабрики, поскольку они - особый случай.
  4. ^ Этот класс является подклассом диктовать, встроенная в Python реализация отображений или словарей.
  5. ^ Если необязательное ключевое слово новый опущено.

Рекомендации

  1. ^ Гамма, Эрих (1994). Шаблоны проектирования. Эддисон-Уэсли. С. 18–19. ISBN  9780321700698.
  2. ^ "Заводской образец ", OODesign.com
  3. ^ Заводской образец, WikiWikiWeb
  4. ^ а б Глава 4. Заводской паттерн: выпечка с использованием объектно-ориентированного подхода: Определение Simple Factory
  5. ^ "30.8 Классы - это объекты: общие фабрики объектов ", Изучение Python, Марка Лутца, 4-е издание, O'Reilly Media, Inc., ISBN  978-0-596-15806-4
  6. ^ Заводской метод, WikiWikiWeb
  7. ^ объекты defaultdict
  8. ^ Перья, Майкл (октябрь 2004 г.). Эффективная работа с устаревшим кодом. Река Аппер Сэдл, Нью-Джерси: Профессиональный технический справочник Prentice Hall. ISBN  978-0-13-117705-5.
  9. ^ Перья, Майкл (Октябрь 2004 г.), Эффективная работа с устаревшим кодом, Верхняя река Сэдл, Нью-Джерси: Профессиональный технический справочник Prentice Hall, ISBN  978-0-13-117705-5
  10. ^ Агербо, Эллен; Корнилс, Айно (1998). «Как сохранить преимущества шаблонов проектирования». Конференция по языкам и приложениям систем объектно-ориентированного программирования. Ванкувер, Британская Колумбия, Канада: ACM: 134–143. ISBN  1-58113-005-8.