Применить паттерн декоратор, чтобы обернуть класс запроса, не меняя его
Дан класс GetRequest с методом async do(url, options), выполняющим GET через fetch. Не меняя GetRequest, добавьте два поведения с помощью структурного паттерна GoF decorator (декоратор): LoggerDecorator, логирующий каждый URL перед запросом, и AuthDecorator, подставляющий заголовок Authorization. Декораторы должны быть композируемыми (один можно обернуть в другой) и предоставлять тот же интерфейс do(url, options), что и обёрнутый объект.
class GetRequest {
async do(url, options) {
return fetch(url, { ...options, method: 'GET' });
}
}
Допишите реализацию.
Определить базовый Decorator, хранящий обёрнутый объект и проксирующий do к нему. Каждый конкретный декоратор наследует его, добавляет своё поведение, затем зовёт super.do(...): LoggerDecorator логирует URL, AuthDecorator дописывает заголовок Authorization. Раз у всех декораторов одна сигнатура do, их можно композировать — new LoggerDecorator(new AuthDecorator(new GetRequest())) — а обёрнутый класс не меняется.
- ✗Править обёрнутый класс вместо его оборачивания, что нарушает суть паттерна (открыт для расширения, закрыт для изменения)
- ✗Ломать общий интерфейс
do(url, options), из-за чего декораторы перестают оборачивать друг друга - ✗Забывать вызвать
super.do(...), из-за чего внутренний запрос так и не выполняется
- →Как одинаковая сигнатура
doу каждого декоратора позволяет складывать их в любом порядке? - →Чем паттерн декоратор отличается от простого наследования от
GetRequest?
Решение
Базовый Decorator хранит обёрнутый объект и по умолчанию делегирует ему do; конкретные декораторы добавляют поведение и зовут super.do.
class Decorator {
constructor(request) {
this.request = request;
}
async do(url, options) {
return this.request.do(url, options);
}
}
class LoggerDecorator extends Decorator {
async do(url, options) {
console.info(`[Logger] GET ${url}`);
return super.do(url, options);
}
}
class AuthDecorator extends Decorator {
async do(url, options) {
const headers = { ...options?.headers, Authorization: 'Bearer token' };
return super.do(url, { ...options, headers });
}
}
const request = new LoggerDecorator(new AuthDecorator(new GetRequest()));
request.do('/api/items');
Как это работает
Decorator хранит ссылку на обёрнутый объект и по умолчанию просто перенаправляет вызов do. Каждый конкретный декоратор наследует его, делает своё дело — пишет в лог или дописывает заголовок — и затем зовёт super.do(...), передавая управление дальше по цепочке.
Ключ к композиции — единая сигнатура do(url, options) у всех участников: и у GetRequest, и у любого декоратора. Поэтому декоратор может обернуть как сам запрос, так и другой декоратор, и порядок оборачивания задаёт порядок выполнения. Исходный GetRequest при этом не изменён — паттерн расширяет поведение, не трогая код класса.