В объектно-ориентированном программировании (ООП), factory - это объект для создания других объектов - формально фабрика - это функция или метод, который возвращает объекты изменяющегося прототипа или класса из некоторых вызов метода, который считается «новым». В более широком смысле подпрограмма , которая возвращает «новый» объект, может называться «фабрикой», как в фабричном методе или фабричной функции. Это основная концепция ООП, которая составляет основу для ряда связанных шаблонов проектирования программного обеспечения.
В программировании на основе классов фабрика является абстракцией конструктора класса, тогда как в программировании на основе прототипов фабрика является абстракцией объекта-прототипа. Конструктор конкретен в том, что он создает объекты как экземпляры одного класса и с помощью указанного процесса (создание экземпляра класса), в то время как фабрика может создавать объекты, создавая экземпляры различных классов или используя другие схемы распределения, такие как объект бассейн. Конкретный объект-прототип состоит в том, что он используется для создания объектов путем клонирования, в то время как фабрика может создавать объекты путем клонирования различных прототипов или с помощью других схем размещения.
Фабрики могут быть вызваны различными способами, чаще всего вызовом метода (фабричный метод ), иногда вызываемым как функция, если фабрика является функциональным объектом (заводская функция). В некоторых языках фабрики являются обобщением конструкторов, то есть конструкторы сами по себе являются фабриками, и они вызываются таким же образом. В других языках фабрики и конструкторы вызываются по-разному, например, с использованием ключевого слова new
для вызова конструкторов, но обычного вызова метода для вызова фабрик; в этих языках фабрики - это абстракция конструкторов, но не строго обобщение, поскольку конструкторы сами по себе не являются фабриками.
Терминология отличается в отношении того, является ли сама концепция фабрики шаблоном проектирования - в основополагающей книге Шаблоны проектирования нет «шаблона фабрики», но вместо этого два шаблона (шаблон фабричного метода и абстрактный фабричный шаблон ), которые используют фабрики. Некоторые источники называют эту концепцию фабричным шаблоном, в то время как другие считают саму концепцию идиомой программирования, зарезервировав термин «фабричный образец» или «фабричный образец» для более сложных шаблонов, которые использовать фабрики, чаще всего паттерн фабричного метода; в этом контексте саму концепцию фабрики можно назвать простой фабрикой. В других контекстах, особенно в языке Python, используется сама «фабрика», как в этой статье. В более широком смысле «фабрика» может применяться не только к объекту, который возвращает объекты из вызова некоторого метода, но и к подпрограмме , которая возвращает объекты, как в фабричной функции (даже если функции не являются объектами) или заводской метод. Поскольку во многих языках фабрики вызываются путем вызова метода, общую концепцию фабрики часто путают с конкретным шаблоном проектирования фабричного метода.
ООП обеспечивает полиморфизм при использовании объекта с помощью отправки метода, формально полиморфизм подтипа с помощью однократной отправки определяется типом объекта, для которого вызывается метод. Однако это не работает для конструкторов, поскольку конструкторы создают объект некоторого типа, а не используют существующий объект. Более конкретно, когда вызывается конструктор, еще нет объекта для отправки.
Использование фабрик вместо конструкторов или прототипов позволяет использовать полиморфизм для создания объекта, а не только для использования объекта. В частности, использование фабрик обеспечивает инкапсуляцию и означает, что код не привязан к конкретным классам или объектам, и, таким образом, иерархия классов или прототипы могут быть изменены или подвергнуты рефакторингу без необходимости изменения кода, который использует их - они абстрагируются от иерархии классов или прототипов.
С технической точки зрения, на языках, где фабрики обобщают конструкторы, фабрики обычно могут использоваться везде, где могут быть конструкторы, а это означает, что интерфейсы, которые принимают конструктор, могут также вообще принимать фабрику - обычно требуется только что-то, что создает объект вместо того, чтобы указывать класс и создание экземпляра.
Например, в Python у класса collections.defaultdict
есть конструктор, который создает объект типа defaultdict
, значения по умолчанию которого создаются путем вызова фабрики. Фабрика передается в качестве аргумента конструктору и сама может быть конструктором или любым предметом, который ведет себя как конструктор - вызываемым объектом, который возвращает объект, то есть фабрику. Например, используя конструктор list
для списков:
# collections.defaultdict ([default_factory [,...]]) d = defaultdict (list)
Фабричные объекты используются в ситуациях, когда получение объекта определенного типа является более сложным процессом, чем простое создание нового объекта, особенно если требуется сложное выделение или инициализация. Некоторые из процессов, необходимых для создания объекта, включают определение того, какой объект создать, управление временем жизни объекта и управление специализированными задачами создания и удаления объекта. Объект фабрики может решить создать объект class (если применимо) динамически, вернуть его из пула объектов , выполнить сложную настройку объекта и т. Д. Точно так же, используя это определение, синглтон , реализованный с помощью шаблона синглтона, является формальной фабрикой - он возвращает объект, но не создает новые объекты, кроме одного экземпляра.
Простейшим примером фабрики является простая фабричная функция, которая просто вызывает конструктор и возвращает результат. В Python фабричная функция f
, которая создает экземпляр класса A
, может быть реализована как:
def f (): return A ()
Простая фабричная функция, реализующая шаблон singleton:
def f (): if f.obj is None: f.obj = A () return f.obj f.obj = None
Это создаст объект при первом вызове и всегда будет возвращать тот же объект после этого.
Фабрики могут быть вызваны различными способами, чаще всего вызовом метода (фабричный метод), иногда вызываемым как функция, если фабрика является вызываемым объектом (фабричная функция). В некоторых языках конструкторы и фабрики имеют идентичный синтаксис, в то время как в других конструкторы имеют особый синтаксис. В языках, где конструкторы и фабрики имеют идентичный синтаксис, таких как Python, Perl, Ruby, Object Pascal и F #, конструкторы могут быть прозрачно заменены фабриками. В языках, где они различаются, их нужно различать по интерфейсам, а переключение между конструкторами и фабриками требует изменения вызовов.
В языках, где объекты выделяются динамически, как в Java или Python, фабрики семантически эквивалентны конструкторам. Однако в таких языках, как C ++, которые позволяют статически выделять некоторые объекты, фабрики отличаются от конструкторов для статически распределенных классов, поскольку у последних может быть выделение памяти, определяемое во время компиляции, в то время как распределение возвращаемых значений фабрик должно определяться в время выполнения. Если конструктор может быть передан в качестве аргумента функции, то вызов конструктора и выделение возвращаемого значения должны выполняться динамически во время выполнения и, таким образом, иметь аналогичную или идентичную семантику для вызова фабрики.
Фабрики используются в различных шаблонах проектирования, в частности в шаблонах создания, таких как. Были разработаны специальные рецепты для их реализации на многих языках. Например, несколько «шаблонов GoF », таких как «шаблон фабричного метода », «Builder » или даже «Singleton "являются реализациями этой концепции. Вместо этого «Абстрактный шаблон фабрики » представляет собой метод построения коллекций фабрик.
В некоторых шаблонах проектирования объект-фабрика имеет метод для каждого типа объекта, который он способен создать. Эти методы дополнительно принимают параметры, определяющие, как создается объект, а затем возвращают созданный объект.
Фабричные объекты распространены в наборах инструментов и фреймворках, где библиотечный код должен создавать объекты типов, которые могут быть подклассы приложениями, использующими фреймворк. Они также используются в управляемой тестированием разработке, чтобы разрешить тестирование классов.
Фабрики определяют фактический конкретный тип объекта, который будет создан, и здесь фактически создается объект. Поскольку фабрика возвращает объекту только абстрактный интерфейс, клиентский код не знает - и не обременен - фактическим конкретным типом только что созданного объекта. Однако абстрактной фабрике известен тип конкретного объекта. В частности, это означает:
Фабрики можно использовать, когда:
Фабрики, в частности фабричные методы, распространены в инструментах и фреймворках, где код библиотеки должен создавать объекты типов, которые могут быть подклассами приложений, использующих фреймворк.
Параллельные иерархии классов часто требуют, чтобы объекты из одной иерархии могли создавать соответствующие объекты из другой.
Заводские методы используются в разработке, управляемой тестированием, чтобы разрешить тестирование классов. Если такой класс Foo
создает другой объект Dangerous
, который не может быть помещен в автоматизированные модульные тесты (возможно, он взаимодействует с производственной базой данных, которая не всегда available), то создание Dangerous
объектов помещается в виртуальный фабричный метод createDangerous
в классе Foo
. Затем для тестирования создается TestFoo
(подкласс Foo
) с переопределением метода виртуальной фабрики createDangerous
для создания и возврата FakeDangerous
, поддельный объект. Затем модульные тесты используют TestFoo
для проверки функциональности Foo
, не вызывая побочных эффектов использования реального объекта Dangerous
.
Помимо использования в шаблонах проектирования, фабрики, особенно фабричные методы, имеют различные преимущества и вариации.
Заводской метод имеет собственное имя. Во многих объектно-ориентированных языках конструкторы должны иметь то же имя, что и класс, в котором они находятся, что может привести к неоднозначности, если существует более одного способа создания объекта (см. перегрузка ). Заводские методы не имеют такого ограничения и могут иметь описательные имена; их иногда называют альтернативными конструкторами . Например, когда комплексные числа создаются из двух действительных чисел, действительные числа можно интерпретировать как декартовы или полярные координаты, но с использованием фабричных методов смысл ясен, как показано в следующем примере на C #.
сложный общественный класс {общественный двойной реальный; общественное двойное воображаемое; общедоступный статический комплекс FromCartesian (двойное действительное, двойное воображаемое) {return new Complex (реальный, мнимый); } общедоступный статический комплекс FromPolar (двойной модуль, двойной угол) {вернуть новый комплекс (модуль * Math.Cos (угол), модуль * Math.Sin (угол)); } частный комплекс (двойное действительное, двойное воображаемое) {this.real = real; this.imaginary = воображаемый; }} Сложный продукт = Complex.FromPolar (1, Math.PI);
Когда фабричные методы используются для разрешения подобной неоднозначности, необработанные конструкторы часто становятся закрытыми, чтобы заставить клиентов использовать фабричные методы.
Заводские методы инкапсулируют создание объектов. Это может быть полезно, если процесс создания очень сложен; например, если это зависит от настроек в файлах конфигурации или от ввода пользователя.
Рассмотрим в качестве примера программу, которая читает файлы изображений. Программа поддерживает разные форматы изображений, представленные классом читателя для каждого формата.
Каждый раз, когда программа считывает изображение, ей необходимо создать считыватель соответствующего типа на основе некоторой информации в файле. Эта логика может быть инкапсулирована в фабричный метод. Этот подход также называют простой фабрикой.
открытый класс ImageReaderFactory {общедоступный статический ImageReader createImageReader (ImageInputStreamProcessor iisp) {if (iisp.isGIF ()) {вернуть новый GifReader (iisp.getInputStream ()); } else if (iisp.isJPEG ()) {вернуть новый JpegReader (iisp.getInputStream ()); } else {throw new IllegalArgumentException ("Неизвестный тип изображения."); }}}
class Factory {сборка общедоступной статической функции ($ type) {$ class = "Format". $ type; if (! class_exists ($ class)) {throw new Exception ("Отсутствует класс формата."); } вернуть новый $ class; }} интерфейс FormatInterface {} класс FormatString реализует FormatInterface {} класс FormatNumber реализует FormatInterface {} try {$ string = Factory :: build ("String"); } catch (исключение $ e) {echo $ e->getMessage (); } попробуйте {$ number = Factory :: build ("Number"); } catch (исключение $ e) {echo $ e->getMessage (); }
Есть три ограничения, связанные с использованием фабричного метода. Первый относится к рефакторингу существующего кода; два других относятся к расширению класса.
Complex c = new Complex (-1, 0);
StrangeComplex
расширяет Complex
, то, если StrangeComplex
не предоставляет свою собственную версию всех фабричных методов, вызов StrangeComplex.FromPolar (1, Math.Pi);даст экземпляр
Complex
(суперкласс), а не ожидаемый экземпляр подкласса. Функции отражения некоторых языков позволяют избежать этой проблемы.Все три проблемы можно решить, изменив базовый язык программирования, чтобы сделать фабрики первоклассными членами (см. Также Виртуальный класс ).