Паттерны проектирования
Классические паттерны проектирования на JavaScript — фабричные функции, абстрактная фабрика и декоратор, реализованные через замыкания и классы ES6.
3 вопросов
JuniorТеорияОчень частоЧто такое фабричная функция и чем она отличается от конструктора с new?
Что такое фабричная функция и чем она отличается от конструктора с new?
Фабричная функция — это любая функция, которая создаёт и возвращает новый объект без new: createUser('Ann') вернёт собранный объект. В отличие от конструктора с new (он создаёт объект, привязывает this и настраивает прототип), у фабрики нет привязки this и автоматической связи с прототипом; она сама решает, что вернуть, и может хранить приватное состояние в замыкании.
Типичные ошибки
- ✗Думать, что фабрику надо вызывать через
new— она сама возвращает объект, поэтомуnewне нужен - ✗Считать, что объекты от фабрики автоматически делят прототип, как созданные через
new - ✗Забывать, что фабрика умеет инкапсулировать приватное состояние через замыкание, чего обычный конструктор так чисто не может
Уточняющие вопросы
- →Как фабричная функция спрячет приватный счётчик, который возвращённый объект может читать, а внешний код — нет?
- →Когда фабричная функция предпочтительнее класса ES6?
MiddleКодИногдаРеализовать абстрактную фабрику, создающую типизированные экземпляры
Реализовать абстрактную фабрику, создающую типизированные экземпляры
Базовый create бросает исключение, чтобы абстрактный класс нельзя было использовать напрямую. В CarFactory.create через switch по type вернуть new Sedan() или new Coupe(), иначе бросить ошибку. Вызывающий код держит ссылку на VehicleFactory и зовёт create, поэтому подмена другого подкласса фабрики меняет семейство продуктов без правок этого кода.
Типичные ошибки
- ✗Позволять базовому
createчто-то возвращать вместо исключения, из-за чего абстрактную фабрику можно вызвать напрямую - ✗Возвращать обычные литералы объектов вместо настоящих экземпляров
Sedan/Coupe, теряя цепочку прототипов иinstanceof - ✗Забывать ветку
default/else, из-за чего неизвестный тип молча возвращаетundefinedвместо исключения
Уточняющие вопросы
- →Как зависимость от
VehicleFactory, а не отnew Sedan(), упрощает расширение вызывающего кода? - →Как добавить второе семейство продуктов, скажем
TruckFactory, не меняя существующих вызовов?
MiddleКодИногдаПрименить паттерн декоратор, чтобы обернуть класс запроса, не меняя его
Применить паттерн декоратор, чтобы обернуть класс запроса, не меняя его
Определить базовый Decorator, хранящий обёрнутый объект и проксирующий do к нему. Каждый конкретный декоратор наследует его, добавляет своё поведение, затем зовёт super.do(...): LoggerDecorator логирует URL, AuthDecorator дописывает заголовок Authorization. Раз у всех декораторов одна сигнатура do, их можно композировать — new LoggerDecorator(new AuthDecorator(new GetRequest())) — а обёрнутый класс не меняется.
Типичные ошибки
- ✗Править обёрнутый класс вместо его оборачивания, что нарушает суть паттерна (открыт для расширения, закрыт для изменения)
- ✗Ломать общий интерфейс
do(url, options), из-за чего декораторы перестают оборачивать друг друга - ✗Забывать вызвать
super.do(...), из-за чего внутренний запрос так и не выполняется
Уточняющие вопросы
- →Как одинаковая сигнатура
doу каждого декоратора позволяет складывать их в любом порядке? - →Чем паттерн декоратор отличается от простого наследования от
GetRequest?