Добавить в корзинуПозвонить
Найти в Дзене
ZDG

Некоторые дополнения к шаблону проектирования Фабрика

Шаблон Фабрика ранее освещался здесь: Вкратце повторю основную идею. Фабрика по нашему запросу должна сгенерировать нужный нам объект. Наивная реализация на JS выглядит так: Почему бы не создать объект класса Wasp напрямую, написав: const obj = new Wasp(); ? Потому что мы используем фабрику тогда, когда не можем прямо в коде написать конкретное имя класса. Например, мы просим пользователя ввести либо 'ant', либо 'wasp', либо 'bee'. И в зависимости от того, что он ввёл, уже создать объект нужного класса. Тогда нам придётся писать условия: если пользователь ввёл 'ant', то создадим new Ant(), если ввёл 'wasp', то создадим new Wasp(), и т.д. И чтобы не повторять условия каждый раз, когда в них возникает необходимость, мы переносим их в фабрику. И теперь всегда обращаемся к фабрике как к единственному источнику объектов, чтобы всё было под контролем. Далее, в материале про фабрику поднимался вопрос, как избавиться от условий внутри фабрики. Один из способов это использовать механизм, которы
Оглавление

Шаблон Фабрика ранее освещался здесь:

Вкратце повторю основную идею. Фабрика по нашему запросу должна сгенерировать нужный нам объект. Наивная реализация на JS выглядит так:

Почему бы не создать объект класса Wasp напрямую, написав:

const obj = new Wasp();

?

Потому что мы используем фабрику тогда, когда не можем прямо в коде написать конкретное имя класса. Например, мы просим пользователя ввести либо 'ant', либо 'wasp', либо 'bee'. И в зависимости от того, что он ввёл, уже создать объект нужного класса.

Тогда нам придётся писать условия: если пользователь ввёл 'ant', то создадим new Ant(), если ввёл 'wasp', то создадим new Wasp(), и т.д.

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

Далее, в материале про фабрику поднимался вопрос, как избавиться от условий внутри фабрики. Один из способов это использовать механизм, который позволяет динамически получить класс из строки.

Проще всего привести пример из PHP:

-2

Мы берём строку, введённую пользователем, и преобразуем её в стандартное имя класса: переводим в нижний регистр, а первый символ делаем заглавным. Таким образом, из 'wasp' получается 'Wasp'. Затем просто создаём объект, используя строку как название класса. Это динамически полученный класс.

Выгода здесь в том, что условия не нужны – имя класса получается непосредственно из переданной строки. Проблемы же такие: во-первых, мы должны поддерживать прямое соответствие между наименованиями классов и строковыми идентификаторами. Например, мы не сможем получить класс Wasp из строки 'waspy'. Вторая проблема это безопасность. Можно передать строку, из которой получается несуществующий класс, что приведёт к падению программы. Либо же строку, которая порождает совсем не тот класс, который предполагался. Короче, здесь нужна какая-то обёртка, которая проверяет наличие и валидность создаваемого класса.

Ещё один способ организации фабрики

Если у нас есть 100 вариантов объектов, то в наивном варианте придётся написать 100 условных операторов. Таким образом, "поиск" нужного класса потребует до 100 сравнений.

Вместо этого мы можем сложить строки и классы в коллекцию типа "карта". Организовать её можно разными способами, я ограничусь простыми свойствами JS-объекта:

-3

Фабрика создаёт у себя реестр, в котором хранятся пары ключ-значение. Поиск в нём занимает типично 1 операцию (см. хэш-таблицы), но в любом случае меньше, чем полный перебор. Дополнительно мы получаем список фабричных объектов, организованный более наглядно, чем частокол условных операторов.

Но есть нюанс

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

В этом случае мы создадим объекты-конструкторы объектов:

-4

и поместим их в реестр фабрики вместо самих объектов:

-5

Теперь при обращении к фабрике она найдёт и вызовет конструктор соответствующего класса, который и породит нужный объект – каждый раз новый.

Данный механизм легко транслируется на другие языки, так как по сути мы сохраняем указатели на функции, что так или иначе реализуется практически везде.

Дополнительно мы получаем более декларативный, чем императивный стиль фабрики.

Мы, конечно, можем обойтись без отдельного описания конструкторов, использовав вместо них безымянные функции:

-6

Но это уже дело предпочтений и особенностей проекта. Имея отдельный конструктор, будет удобнее делать в нём тяжёлую инициализацию объекта, наподобие:

-7

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