В объектно-ориентированном программировании (ООП) , завод является объектом для создания других объектов - формально заводом является функцией или метод , который возвращает объекты меняющегося прототипа или класса [1] из некоторого вызова метода, который , как предполагается, будет " новый". [a] В более широком смысле подпрограмма, которая возвращает «новый» объект, может называться «фабрикой», как в фабричном методе или фабричной функции . Это основная концепция ООП, которая составляет основу ряда связанных шаблонов проектирования программного обеспечения .
Мотивация
В программировании на основе классов , завод является абстракцией из конструктора класса, в то время как в прототипе на базе программирования завода является абстракцией объекта прототипа. Конструктор конкретен в том смысле, что он создает объекты как экземпляры одного класса и с помощью указанного процесса (создание экземпляров класса), в то время как фабрика может создавать объекты, создавая экземпляры различных классов или используя другие схемы распределения, такие как пул объектов . Конкретный объект-прототип состоит в том, что он используется для создания объектов путем клонирования , в то время как фабрика может создавать объекты путем клонирования различных прототипов или с помощью других схем размещения.
Фабрики могут быть вызваны различными способами, чаще всего вызовом метода ( фабричный метод ), иногда вызываемым как функция, если фабрика является функциональным объектом ( фабричная функция ). В некоторых языках фабрики являются обобщением конструкторов, то есть конструкторы сами являются фабриками, и они вызываются таким же образом. В других языках фабрики и конструкторы вызываются по-разному, например, с использованием ключевого слова new
для вызова конструкторов, но обычного вызова метода для вызова фабрик; в этих языках фабрики - это абстракция конструкторов, но не строго обобщение, поскольку конструкторы сами по себе не являются фабриками.
Терминология
Терминология отличается в отношении того, является ли сама концепция фабрики шаблоном проектирования - в шаблонах проектирования нет «шаблона фабрики», а есть два шаблона ( шаблон фабричного метода и абстрактный фабричный шаблон ), которые используют фабрики. Некоторые источники называют концепцию фабричным шаблоном , [2] [3] в то время как другие считают саму концепцию идиомой программирования , [4] оставляя термин «фабричный образец» или «фабричный образец» для более сложных образцов, использующих фабрики, чаще всего паттерн фабричного метода; в этом контексте саму концепцию фабрики можно назвать простой фабрикой. [4] В других контекстах, особенно в языке Python, используется сама «фабрика», как в этой статье. [5] В более широком смысле «фабрика» может применяться не только к объекту, который возвращает объекты из вызова некоторого метода, но и к подпрограмме, которая возвращает объекты, как в фабричной функции (даже если функции не являются объектами) или фабричном методе. [6] Поскольку во многих языках фабрики вызываются путем вызова метода, общую концепцию фабрики часто путают с конкретным шаблоном проектирования фабричного шаблона метода .
Использовать
ООП обеспечивает полиморфизм использования объекта посредством отправки метода , формально полиморфизм подтипа посредством однократной отправки, определяемый типом объекта, для которого вызывается метод. Однако это не работает для конструкторов, поскольку конструкторы создают объект определенного типа, а не используют существующий объект. Более конкретно, когда вызывается конструктор, еще нет объекта для отправки. [b]
Использование фабрик вместо конструкторов или прототипов позволяет использовать полиморфизм для создания объекта, а не только для использования объекта. В частности, использование фабрик обеспечивает инкапсуляцию и означает, что код не привязан к конкретным классам или объектам, и, таким образом, иерархия классов или прототипы могут быть изменены или рефакторинги без необходимости изменять код, который их использует - они абстрагируются от иерархии классов или прототипов.
С технической точки зрения, на языках, где фабрики обобщают конструкторы, фабрики обычно могут использоваться везде, где могут быть конструкторы, [c] означает, что интерфейсы, которые принимают конструктор, также могут вообще принимать фабрику - обычно требуется только что-то, что создает объект, а не необходимо указать класс и экземпляр.
Например, в Python collections.defaultdict
класс [7] имеет конструктор, который создает объект типа defaultdict
[d] , значения по умолчанию которого создаются путем вызова фабрики. Фабрика передается в качестве аргумента конструктору и сама может быть конструктором или любой вещью, которая ведет себя как конструктор - вызываемым объектом, который возвращает объект, т. Е. Фабрику. Например, используя list
конструктор для списков:
# collections.defaultdict ([default_factory [, ...]]) d = defaultdict ( список )
Создание объекта
Фабричные объекты используются в ситуациях, когда получение объекта определенного типа является более сложным процессом, чем простое создание нового объекта, особенно если требуется сложное выделение или инициализация. Некоторые из процессов, требуемых при создании объекта, включают определение того, какой объект создать, управление временем жизни объекта и управление специализированными задачами создания и удаления объекта. Объект-фабрика может решить создать класс объекта (если применимо) динамически, вернуть его из пула объектов , выполнить сложную настройку объекта и т. Д. Точно так же, используя это определение, синглтон, реализованный с помощью шаблона синглтона, является формальной фабрикой - он возвращает объект, но не создает новые объекты за пределами единственного экземпляра.
Примеры
Простейшим примером фабрики является простая фабричная функция, которая просто вызывает конструктор и возвращает результат. В Python фабричная функция, f
которая создает экземпляр класса, A
может быть реализована как:
def f (): вернуть A ()
Простая фабричная функция, реализующая шаблон singleton:
def f (): если f . obj - Нет : f . obj = A () return f . объектf . obj = Нет
Это создаст объект при первом вызове и после этого всегда будет возвращать тот же объект.
Синтаксис
Фабрики могут быть вызваны различными способами, чаще всего вызовом метода ( фабричный метод ), иногда вызываемым как функция, если фабрика является вызываемым объектом ( фабричная функция ). В некоторых языках конструкторы и фабрики имеют идентичный синтаксис, в то время как в других конструкторы имеют особый синтаксис. В языках, где конструкторы и фабрики имеют идентичный синтаксис, таких как Python, Perl, Ruby, Object Pascal и F #, конструкторы [e] могут быть прозрачно заменены фабриками. В языках, где они различаются, их нужно различать по интерфейсам, а переключение между конструкторами и фабриками требует изменения вызовов.
Семантика
В языках, где объекты выделяются динамически , как в Java или Python, фабрики семантически эквивалентны конструкторам. Однако в таких языках, как C ++, которые позволяют статически выделять некоторые объекты, фабрики отличаются от конструкторов для статически распределенных классов, поскольку у последних может быть выделение памяти, определяемое во время компиляции, в то время как распределение возвращаемых значений фабрик должно определяться в время выполнения. Если конструктор может быть передан в качестве аргумента функции, то вызов конструктора и выделение возвращаемого значения должны выполняться динамически во время выполнения и, таким образом, иметь аналогичную или идентичную семантику для вызова фабрики.
Шаблоны проектирования
Фабрики используются в различных шаблонах проектирования , в частности в шаблонах создания, таких как библиотека объектов шаблонов проектирования . Были разработаны конкретные рецепты для их реализации на многих языках. Например, несколько « шаблонов GoF », таких как « шаблон фабричного метода », « Builder » или даже « Singleton », являются реализациями этой концепции. Вместо этого " абстрактный фабричный паттерн " - это метод построения коллекций фабрик.
В некоторых шаблонах проектирования фабричный объект имеет метод для каждого типа объекта, который он способен создать. Эти методы дополнительно принимают параметры, определяющие способ создания объекта, а затем возвращают созданный объект.
Приложения
Фабричные объекты часто встречаются в наборах инструментов и фреймворках, где библиотечный код должен создавать объекты типов, которые могут быть подклассами приложений, использующих фреймворк. Они также используются при разработке, управляемой тестированием, чтобы можно было проверять классы. [8]
Фабрики определяют фактический конкретный тип создаваемого объекта , и именно здесь объект фактически создается. Поскольку фабрика возвращает объекту только абстрактный интерфейс, клиентский код не знает - и не обременен - фактическим конкретным типом только что созданного объекта. Однако абстрактной фабрике известен тип конкретного объекта. В частности, это означает:
- Клиентский код ничего не знает о конкретном типе , и ему не нужно включать какие-либо файлы заголовков или объявления классов, относящиеся к конкретному типу. Клиентский код имеет дело только с абстрактным типом. Объекты конкретного типа действительно создаются фабрикой, но клиентский код обращается к таким объектам только через их абстрактный интерфейс .
- Добавление новых конкретных типов выполняется путем модификации клиентского кода для использования другой фабрики, модификация, которая обычно представляет собой одну строку в одном файле. Это значительно проще, чем изменение клиентского кода для создания экземпляра нового типа, что потребовало бы изменения каждого места в коде, где создается новый объект.
Применимость
Фабрики можно использовать, когда:
- Создание объекта делает невозможным повторное использование без значительного дублирования кода.
- Создание объекта требует доступа к информации или ресурсам, которые не должны содержаться в составном классе.
- Управление жизненным циклом сгенерированных объектов должно быть централизованным, чтобы гарантировать согласованное поведение в приложении.
Фабрики, в частности фабричные методы, распространены в наборах инструментов и фреймворках , где библиотечный код должен создавать объекты типов, которые могут быть подклассами приложений, использующих фреймворк.
Иерархии параллельных классов часто требуют, чтобы объекты из одной иерархии могли создавать соответствующие объекты из другой.
Фабричные методы используются при разработке, управляемой тестированием, чтобы позволить тестировать классы. [9] Если такой класс Foo
создает другой объект, Dangerous
который не может быть подвергнут автоматическим модульным тестам (возможно, он взаимодействует с производственной базой данных, которая не всегда доступна), то создание Dangerous
объектов помещается в метод виртуальной фабрики createDangerous
в классе Foo
. Для тестирования TestFoo
(подкласс Foo
) Затем создаются, с помощью метода виртуальной фабрики createDangerous
переопределена для создания и возврата FakeDangerous
, а объекта поддельного . Затем модульные тесты используются TestFoo
для проверки функциональности Foo
без побочных эффектов использования реального Dangerous
объекта.
Преимущества и варианты
Помимо использования в шаблонах проектирования, фабрики, особенно фабричные методы, имеют различные преимущества и вариации.
Описательные имена
У фабричного метода есть собственное имя. Во многих объектно-ориентированных языках конструкторы должны иметь то же имя, что и класс, в котором они находятся, что может привести к неоднозначности, если существует более одного способа создания объекта (см. Перегрузку ). Фабричные методы не имеют такого ограничения и могут иметь описательные имена; их иногда называют альтернативными конструкторами . Например, когда комплексные числа создаются из двух действительных чисел, действительные числа можно интерпретировать как декартовы или полярные координаты, но с использованием фабричных методов смысл ясен, как показано в следующем примере на C #.
публичный класс Complex { публичный двойной реальный ; общественное двойное воображаемое ; общедоступный статический комплекс FromCartesian ( двойное действительное , двойное мнимое ) { вернуть новый комплекс ( реальный , мнимый ); } общедоступный статический комплекс FromPolar ( двойной модуль , двойной угол ) { вернуть новый комплекс ( модуль * Math . Cos ( угол ), модуль * Math . Sin ( угол )); } частный комплекс ( двойное действительное , двойное воображаемое ) { это . реальный = реальный ; это . мнимое = мнимое ; } }Сложный продукт = Сложный . FromPolar ( 1 , Math . PI );
Когда фабричные методы используются для разрешения подобной неоднозначности, необработанные конструкторы часто становятся закрытыми, чтобы заставить клиентов использовать фабричные методы.
Инкапсуляция
Фабричные методы инкапсулируют создание объектов. Это может быть полезно, если процесс создания очень сложен; например, если это зависит от настроек в файлах конфигурации или от ввода пользователя.
Рассмотрим в качестве примера программу, которая читает файлы изображений . Программа поддерживает различные форматы изображений, представленные классом чтения для каждого формата.
Каждый раз, когда программа считывает изображение, ей необходимо создать считыватель соответствующего типа на основе некоторой информации в файле. Эта логика может быть инкапсулирована в фабричный метод. Этот подход также называют простой фабрикой.
Ява
открытый класс ImageReaderFactory { общедоступный статический ImageReader createImageReader ( ImageInputStreamProcessor iisp ) { if ( iisp . isGIF ()) { вернуть новый GifReader ( iisp . getInputStream ()); } else if ( iisp . isJPEG ()) { вернуть новый JpegReader ( iisp . getInputStream ()); } else { выбросить новое исключение IllegalArgumentException ( "Неизвестный тип изображения." ); } } }
PHP
class Factory { сборка общедоступной статической функции ( $ type ) { $ class = "Format" . $ type ; if ( ! class_exists ( $ class )) { throw new Exception ( "Отсутствует класс формата." ); } вернуть новый $ class ; } } интерфейс FormatInterface {}класс FormatString реализует FormatInterface {} класс FormatNumber реализует FormatInterface {}попробуйте { $ string = Factory :: build ( "String" ); } catch ( исключение $ e ) { echo $ e -> getMessage (); }попробуйте { $ number = Factory :: build ( "Число" ); } catch ( исключение $ e ) { echo $ e -> getMessage (); }
Ограничения
Есть три ограничения, связанных с использованием фабричного метода. Первый касается рефакторинга существующего кода; два других относятся к расширению класса.
- Первое ограничение заключается в том, что рефакторинг существующего класса для использования фабрик нарушает работу существующих клиентов. Например, если бы класс Complex был стандартным классом, у него могло бы быть множество клиентов с таким кодом, как:
Комплекс c = новый Комплекс ( - 1 , 0 );
- Как только мы понимаем, что необходимы две разные фабрики, мы меняем класс (на код, показанный ранее ). Но поскольку конструктор теперь частный, существующий клиентский код больше не компилируется.
- Второе ограничение заключается в том, что, поскольку в шаблоне используется частный конструктор, класс не может быть расширен. Любой подкласс должен вызывать унаследованный конструктор, но этого нельзя сделать, если этот конструктор является частным.
- Третье ограничение заключается в том, что если класс должен быть расширен (например, путем защиты конструктора - это рискованно, но выполнимо), подкласс должен обеспечивать собственную повторную реализацию всех фабричных методов с точно такими же сигнатурами. Например, если класс
StrangeComplex
расширяетсяComplex
, то, если неStrangeComplex
предоставляется собственная версия всех заводских методов, вызовдаст экземплярСтранный комплекс . FromPolar ( 1 , Math . Pi );
Complex
(суперкласса), а не ожидаемый экземпляр подкласса. Функции отражения некоторых языков позволяют избежать этой проблемы.
Все три проблемы можно решить, изменив базовый язык программирования, чтобы фабрики стали первоклассными членами класса (см. Также Виртуальный класс ). [10]
Заметки
- ^ С точки зрения интерфейса любой объект, возвращающий объект, может использоваться в качестве фабрики, но семантически фабрика возвращает либо вновь созданный объект, например экземпляр класса или копию прототипа, либо объект, который выглядит новым, например, повторно инициализированный объект из пула объектов.
- ^ В языках, где конструкторы сами являются методами объекта класса ( методы класса ), существует существующий объект, а конструкторы являются частными случаями фабричных методов, причем полиморфное создание является частным случаем отправки полиморфных методов. В других языках существует четкое различие между конструкторами и методами.
- ^ Конструкторы можно использовать везде, где это возможно на фабриках, поскольку они являются частным случаем.
- ^ Этот класс является подклассом
dict
встроенной в Python реализации отображений или словарей. - ^ Если необязательное ключевое слово
new
опущено.
Рекомендации
- ^ Гамма, Эрих (1994). Шаблоны проектирования . Эддисон-Уэсли. С. 18–19. ISBN 9780321700698.
- ^ " Factory Pattern ", OODesign.com
- ^ Заводской шаблон , WikiWikiWeb
- ^ a b Глава 4. Шаблон Factory: выпечка с OO Goodness : определение Simple Factory
- ^ « Классы 30.8 - это объекты: общие фабрики объектов », Learning Python, Марк Лутц, 4-е издание, O'Reilly Media, Inc., ISBN 978-0-596-15806-4
- ^ Заводской метод , WikiWikiWeb
- ^ объекты defaultdict
- ^ Перья, Майкл (октябрь 2004 г.). Эффективная работа с устаревшим кодом . Река Аппер Сэдл, Нью-Джерси: Профессиональный технический справочник Prentice Hall. ISBN 978-0-13-117705-5.
- ^ Фезерс, Майкл (октябрь 2004 г.), Эффективная работа с Legacy Code , Upper Saddle River, NJ: Prentice Hall Professional Technical Reference, ISBN 978-0-13-117705-5
- ^ Агербо, Эллен; Корнилс, Айно (1998). «Как сохранить преимущества шаблонов проектирования». Конференция по языкам и приложениям систем объектно-ориентированного программирования . Ванкувер, Британская Колумбия, Канада: ACM: 134–143. ISBN 1-58113-005-8.
- Эрик, Фриман; Робсон, Элизабет; Бейтс, Берт; Сьерра, Кэти (2009) [2004]. Начните с шаблонов проектирования . В первую очередь . О'Рейли. ISBN 978-0-596-55656-3.