Введение

Объект, создающий другие объекты

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

Мотивация

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

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

Терминология различается в отношении того, является ли сама концепция фабрики шаблоном проектирования – в книге «Design Patterns» нет «шаблона фабрики», а вместо этого есть два шаблона (шаблон фабричного метода и шаблон абстрактной фабрики), которые используют фабрики. Некоторые источники называют эту концепцию шаблоном фабрики, в то время как другие считают саму концепцию идиомой программирования, оставляя термин «шаблон фабрики» или «шаблоны фабрик» для более сложных шаблонов, использующих фабрики, чаще всего шаблон фабричного метода; в этом контексте сама концепция фабрики может называться простой фабрикой. В более широком смысле, термин «фабрика» может применяться не только к объекту, возвращающему объекты при вызове метода, но и к подпрограмме, возвращающей объекты, как в случае фабричной функции (даже если функции не являются объектами) или фабричного метода. Поскольку во многих языках фабрики вызываются через вызов метода, общая концепция фабрики часто смешивается с конкретным шаблоном проектирования «фабричный метод».

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

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

Синтаксис

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

Семантика

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

Конструктивные шаблоны

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

Приложения

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

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

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

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

Инкапсулирование

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

Ограничения

Существует три ограничения, связанные с использованием фабричного метода. Первое связано с рефакторингом существующего кода, а два других – с расширением класса. Первое ограничение заключается в том, что рефакторинг существующего класса для использования фабрик нарушает работу существующих клиентов. Например, если класс Complex был обычным классом, у него могло быть множество клиентов с кодом, подобным этому: `Complex c = new Complex(1, 0);`

Как только мы понимаем, что требуются две разные фабрики, мы изменяем класс (в соответствии с кодом, представленным ранее). Но поскольку конструктор теперь приватный, существующий клиентский код перестает компилироваться. Второе ограничение состоит в том, что, поскольку шаблон основан на использовании приватного конструктора, класс нельзя расширить. Любой подкласс должен вызывать унаследованный конструктор, но это невозможно, если этот конструктор является приватным. Третье ограничение заключается в том, что если класс предполагается расширить (например, сделав конструктор защищенным – это рискованно, но возможно), подкласс должен предоставить собственную реализацию всех фабричных методов с точно такими же сигнатурами. Например, если класс StrangeComplex расширяет Complex, то, если StrangeComplex не предоставит собственную версию всех фабричных методов, вызов `StrangeComplex.FromPolar(1, Math.PI);` вернет экземпляр Complex (суперкласса), а не ожидаемый экземпляр подкласса. Функции рефлексии в некоторых языках программирования позволяют обойти эту проблему. Все три проблемы можно было бы решить, изменив базовый язык программирования, чтобы фабрики стали полноценными членами класса (см. также «Виртуальный класс»).