В объектно-ориентированном программировании фабрика — это объект для создания других объектов ; формально это функция или метод , который возвращает объекты изменяющегося прототипа или класса [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 ()
Простая фабричная функция, реализующая шаблон синглтон:
def f ( ) : если f.obj равен None : f.obj = A ( ) return f.obj f . obj = Нет
При первом вызове будет создан объект, и впоследствии всегда будет возвращаться тот же объект.
Фабрики могут вызываться различными способами, чаще всего вызовом метода ( метод фабрики ), иногда путем вызова в качестве функции, если фабрика является вызываемым объектом ( функция фабрики ). В некоторых языках конструкторы и фабрики имеют идентичный синтаксис, в то время как в других конструкторы имеют особый синтаксис. В языках, где конструкторы и фабрики имеют идентичный синтаксис, таких как Python, Perl , Ruby , Object Pascal и F# , конструкторы [e] могут быть прозрачно заменены фабриками. В языках, где они различаются, необходимо различать их в интерфейсах, а переключение между конструкторами и фабриками требует изменения вызовов.
В языках, где объекты динамически выделяются , как в Java или Python, фабрики семантически эквивалентны конструкторам. Однако в таких языках, как C++ , которые позволяют статически выделять некоторые объекты, фабрики отличаются от конструкторов для статически выделенных классов, поскольку последние могут иметь выделение памяти, определенное во время компиляции, в то время как выделение возвращаемых значений фабрик должно определяться во время выполнения. Если конструктор может быть передан в качестве аргумента функции, то вызов конструктора и выделение возвращаемого значения должны выполняться динамически во время выполнения и, таким образом, иметь схожую или идентичную семантику с вызовом фабрики.
Фабрики используются в различных шаблонах проектирования , в частности в создающих шаблонах , таких как библиотека объектов шаблонов проектирования. Были разработаны специальные рецепты для их реализации во многих языках. Например, несколько шаблонов GoF , такие как шаблон метода фабрики , Builder или даже Singleton , являются реализациями этой концепции. Шаблон Abstract factory вместо этого является методом для построения коллекций фабрик.
В некоторых шаблонах проектирования объект-фабрика имеет метод для каждого типа объекта, который он может создать. Эти методы опционально принимают параметры, определяющие, как создается объект, а затем возвращают созданный объект.
Объекты-фабрики распространены в виджетных инструментах и программных фреймворках , где библиотечный код должен создавать объекты типов, которые могут быть подклассифицированы приложениями, использующими фреймворк. Они также используются в разработке на основе тестирования , чтобы позволить классам проходить тестирование. [8]
Фабрики определяют конкретный тип объекта , который должен быть создан, и именно здесь создается объект. Поскольку фабрика возвращает только абстрактный интерфейс к объекту, клиентский код не знает и не обременен конкретным типом объекта, который был только что создан. Однако тип конкретного объекта известен абстрактной фабрике. В частности, это означает:
Фабрики можно использовать, когда:
Фабрики, в частности фабричные методы, широко распространены в наборах инструментов виджетов и программных фреймворках , где библиотечный код должен создавать объекты типов, которые могут быть подклассифицированы приложениями, использующими фреймворк.
Параллельные иерархии классов часто требуют, чтобы объекты из одной иерархии могли создавать соответствующие объекты из другой.
Фабричные методы используются в разработке на основе тестирования, чтобы позволить проводить тестирование классов. [9] Если такой класс Foo
создает другой объект Dangerous
, который не может быть подвергнут автоматизированным модульным тестам (возможно, он взаимодействует с производственной базой данных, которая не всегда доступна), то создание Dangerous
объектов помещается в виртуальный фабричный метод createDangerous
в классе Foo
. Для тестирования затем создается TestFoo
(подкласс Foo
), с переопределенным виртуальным фабричным методом createDangerous
для создания и возврата FakeDangerous
, поддельного объекта . Затем модульные тесты используют TestFoo
для тестирования функциональности , Foo
не вызывая побочного эффекта использования реального Dangerous
объекта.
Помимо использования в шаблонах проектирования, фабрики, особенно фабричные методы, имеют различные преимущества и вариации.
Фабричный метод имеет отдельное имя. Во многих объектно-ориентированных языках конструкторы должны иметь то же имя, что и класс, в котором они находятся, что может привести к неоднозначности, если существует более одного способа создания объекта (см. перегрузка ). Фабричные методы не имеют такого ограничения и могут иметь описательные имена; иногда их называют альтернативными конструкторами . Например, когда комплексные числа создаются из двух действительных чисел, действительные числа можно интерпретировать как декартовы или полярные координаты, но при использовании фабричных методов смысл ясен, как показано в следующем примере на C# .
открытый класс Complex { открытый double _real ; открытый double _imaginary ; public static Complex FromCartesian ( двойной действительный , двойной мнимый ) { return new Complex ( действительный , мнимый ); } public static Complex FromPolar ( двойной модуль , двойной угол ) { return new Complex ( модуль * Math . Cos ( угол ), модуль * Math . Sin ( угол )); } частный комплекс ( двойной действительный , двойной мнимый ) { this._real = действительный ; this._imaginary = мнимый ; } } Комплексное произведение = Complex.FromPolar ( 1 , Math.PI ) ;
Когда для устранения неоднозначности используются фабричные методы, необработанные конструкторы часто делаются закрытыми, чтобы заставить клиентов использовать фабричные методы.
Фабричные методы инкапсулируют создание объектов. Это может быть полезно, если процесс создания очень сложен; например, если он зависит от настроек в файлах конфигурации или от пользовательского ввода.
Рассмотрим в качестве примера программу, которая читает файлы изображений . Программа поддерживает различные форматы изображений, представленные классом считывателя для каждого формата.
Каждый раз, когда программа считывает изображение, ей необходимо создать считыватель соответствующего типа на основе некоторой информации в файле. Эту логику можно инкапсулировать в фабричный метод. Этот подход также называют Simple Factory.
public class ImageReaderFactory { public static ImageReader createImageReader ( ImageInputStreamProcessoriisp ) { if ( iisp.isGIF ( ) ) { return new GifReader ( iisp.getInputStream ( ) ) ; } else if ( iisp.isJPEG ( ) ) { return new JpegReader ( iisp.getInputStream ( ) ); } else { throw new IllegalArgumentException ( "Неизвестный тип изображения." ); } } }
class Factory { public static function build ( string $type ) : FormatInterface { $class = "Format" . $type ; return new $class ; } }интерфейс ФорматИнтерфейс {}класс FormatString реализует FormatInterface {} класс FormatNumber реализует FormatInterface {}попробуйте { $string = Factory :: build ( "Строка" ); } catch ( Ошибка $e ) { echo $e -> getMessage (); }try { $number = Factory :: build ( "Number" ); } catch ( Ошибка $e ) { echo $e -> getMessage (); }
С использованием метода фабрики связано три ограничения. Первое подразумевает рефакторинг существующего кода; два других предполагают расширение класса.
Комплекс c = новый комплекс ( - 1 , 0 );
StrangeComplex
extends Complex
, то если не StrangeComplex
предоставляет собственную версию всех методов фабрики, вызовStrangeComplex.FromPolar ( 1 , Math.Pi ) ;
Complex
(суперкласса) вместо ожидаемого экземпляра подкласса. Возможности рефлексивного программирования (рефлексии) некоторых языков позволяют избежать этой проблемы.Все три проблемы можно решить, изменив базовый язык программирования, сделав фабрики членами класса первого класса (см. также Виртуальный класс ). [10]
dict
, встроенной реализации сопоставлений или словарей в Python.new
пропущено.