Зачем вообще нужен паттерн Декоратор в JavaScript
Когда в коде появляется сущность, которую хочется «чуть‑чуть улучшить», но переписывать её с нуля страшно и дорого — это почти всегда намёк на паттерн Декоратор в JavaScript. Он позволяет оборачивать объект дополнительным поведением, не ломая исходную реализацию и не плодя монструозное наследование.
Если говорить по‑человечески: Декоратор — это способ «надеть на объект новый костюм», не меняя его «скелет» и не трогая уже написанный код.
Базовая идея паттерна Декоратор на примере
Простейший пример на JavaScript
Начнём с мини‑кейса. Допустим, у нас есть логгер:
```js
class Logger {
log(message) {
console.log(message);
}
}
const logger = new Logger();
logger.log('Привет, мир!');
```
Через пару спринтов заказчик просит:
- писать время;
- отправлять ошибки на внешний сервис;
- местами логировать в файл (условно).
Первая реакция джуна — дописать всё в класс `Logger`. Но так класс быстро превращается в «божественный объект» с кучей if-ов. Вместо этого можно использовать паттерн декоратор JavaScript примеры которого как раз показывают идею «оборачивания»:
```js
class TimestampLogger {
constructor(logger) {
this.logger = logger;
}
log(message) {
const withTime = `[${new Date().toISOString()}] ${message}`;
this.logger.log(withTime);
}
}
const baseLogger = new Logger();
const loggerWithTime = new TimestampLogger(baseLogger);
loggerWithTime.log('Сервис запущен');
```
Мы не трогали исходный `Logger` вообще. Просто «надели» на него ещё один уровень поведения.
Кейс из практики: платёжный сервис
Реальный пример из продакшена. Был микросервис, который ходил в несколько платёжных систем. Базовый клиент умел:
```js
class PaymentClient {
async pay(data) {
// вызов внешнего API
}
}
```
Потом понадобилось:
1. логировать каждый запрос;
2. ретраить неудачные платежи;
3. добавлять трассировку (trace id) для распределённого трейсинга.
Без декораторов появились классы `PaymentClientWithLoggingAndRetryAndTracing` и подобная боль. В какой‑то момент команда остановилась и переписала это так:
```js
class LoggingPaymentClient {
constructor(client, logger) {
this.client = client;
this.logger = logger;
}
async pay(data) {
this.logger.info('Payment started', data);
const result = await this.client.pay(data);
this.logger.info('Payment finished', result);
return result;
}
}
class RetryPaymentClient {
constructor(client, retries = 3) {
this.client = client;
this.retries = retries;
}
async pay(data) {
let attempt = 0;
while (true) {
try {
return await this.client.pay(data);
} catch (e) {
if (++attempt > this.retries) throw e;
}
}
}
}
```
Теперь можно комбинировать:
```js
const base = new PaymentClient();
const withRetry = new RetryPaymentClient(base, 2);
const withLoggingAndRetry = new LoggingPaymentClient(withRetry, logger);
```
Гибко, читаемо и легко расширяемо без каскада наследования.
Сравнение разных подходов: наследование, миксины, декораторы
Подход 1. Наследование классов
Классика:
```js
class LoggerWithTime extends Logger {
log(message) {
super.log(`[${new Date().toISOString()}] ${message}`);
}
}
```
Звучит нормально, пока не нужно три‑четыре разных «фичи» одновременно. Тогда появляются:
- `LoggerWithTimeAndLevel`;
- `LoggerWithTimeAndRemote`;
- `LoggerWithTimeAndLevelAndRemote`.
Комбинаторный взрыв.
Подход 2. Миксины и функциональные обёртки
Иногда вместо ООП‑подхода делают функциональные декораторы:
```js
function withTimeLog(fn) {
return (...args) => {
console.log(new Date().toISOString(), 'ARGS:', args);
return fn(...args);
};
}
function handleRequest(req, res) {
// обработка запроса
}
const handleRequestWithLog = withTimeLog(handleRequest);
```
Это тоже Декоратор, просто в функциональной форме. Подход отличный для чистых функций, middleware в Express/Koa или React‑хуков.
Подход 3. Классический паттерн Декоратор (объект над объектом)
Классический вариант:
1. есть базовый интерфейс (в JS — «договор» по методам);
2. есть объект, реализующий этот интерфейс;
3. есть «обёртки», которые принимают объект и делегируют ему часть работы.
В ECMAScript это может быть и через классы, и через функции, и через прокси — принцип один и тот же.
Декораторы как синтаксис: @decorator в JavaScript
Отдельная история — экспериментальный синтаксис `@decorator`. Он активно обсуждался и эволюционировал несколько лет, и в 2025 году уже стабилизирован как часть современных стандартов (поддержка зависит от движка и сборки).
Упрощённо:
```js
function readonly(target, context) {
if (context.kind === 'field') {
return function (initialValue) {
return Object.freeze(initialValue);
};
}
}
class Config {
@readonly
settings = { featureFlag: true };
}
```
Это другой уровень: декоратор применяется не к объекту, а к свойству/методу во время определения класса. В продакшене это чаще встречается через TypeScript, Babel и курсы по JavaScript паттерн декоратор онлайн активно разбирают именно такой синтаксис, потому что он часто приходит в комплекте с TS.
Плюсы и минусы разных реализаций Декоратора
Плюсы классического объектного декоратора
1. Комбинаторика без боли. Можно свободно соединять разные декораторы, не размножая наследников.
2. Открытость/закрытость. Класс закрыт для изменения, открыт для расширения (классический принцип SOLID), что особенно важно, когда исходный код менять нельзя (например, сторонняя библиотека).
3. Хорошая тестируемость. Каждый декоратор тестируется отдельно, а комбинации собираются как конструктор.
Минусы и реальные подводные камни
С другой стороны, паттерн Декоратор в JavaScript уроки для разработчиков всегда честно предупреждают:
- цепочки декораторов усложняют отладку: стектрейсы длиннее, цепочка вызовов запутаннее;
- нужно следить за тем, чтобы интерфейс не «ломался» при декорировании;
- неправильный нейминг превращает код в слоёный пирог, в котором сложно понять, где настоящий источник поведения.
Кроме того, когда декораторов слишком много и они разношерстные, становится неочевидно, кто что именно делает с входными данными.
Практические кейсы, где Декоратор реально спасает
Кейс 1. API‑клиенты во фронтенде и Node.js

Типичная ситуация: есть обёртка над `fetch` или `axios`. Со временем нужно:
- добавлять токены авторизации;
- логировать запросы;
- кэшировать ответы;
- подменять бэкенд на мок‑сервер для тестов.
Здесь идеально заходит цепочка функциональных декораторов:
```js
const baseFetch = (url, options) => fetch(url, options);
const withAuth = (fetchImpl, getToken) => (url, options = {}) => {
const token = getToken();
const headers = {
...options.headers,
Authorization: `Bearer ${token}`
};
return fetchImpl(url, { ...options, headers });
};
const withLogging = (fetchImpl) => async (url, options) => {
console.log('REQUEST', url, options);
const res = await fetchImpl(url, options);
console.log('RESPONSE', res.status);
return res;
};
const apiFetch = withLogging(withAuth(baseFetch, () => localStorage.getItem('token')));
```
В одном проекте такой подход позволил безболезненно заменить источник токена (из localStorage на cookie + refresh flow) за полдня, не трогая сотни мест использования `apiFetch`.
Кейс 2. UI‑компоненты без боли

В React и похожих фреймворках Декоратор часто проявляется как HOC (Higher‑Order Component), даже если ключевое слово «pattern» никто не называет.
Например, нужно:
- трекать клики по кнопкам (аналитика);
- блокировать кнопку во время запроса;
- добавлять тултип с подсказкой.
Вместо огромного компонента:
```jsx
const TrackedButton = withTracking(
withLoadingState(
withTooltip(BaseButton)
)
);
```
Каждый HOC — это по сути декоратор, подмешивающий поведение. В одном крупном дашборде такой подход позволил унифицировать трекинг и состояние загрузки для десятков кнопок просто через повторное использование декораторов‑обёрток.
Кейс 3. Валидация и нормализация данных
При работе с формами или DTO на бэкенде часто встречается такой шаблон:
```js
class BaseValidator {
validate(data) {
return data;
}
}
class TrimValidator {
constructor(validator) {
this.validator = validator;
}
validate(data) {
const trimmed = Object.fromEntries(
Object.entries(data).map(([k, v]) => [k, typeof v === 'string' ? v.trim() : v])
);
return this.validator.validate(trimmed);
}
}
class EmailValidator {
constructor(validator) {
this.validator = validator;
}
validate(data) {
if (!data.email || !data.email.includes('@')) {
throw new Error('Bad email');
}
return this.validator.validate(data);
}
}
```
Юзкейс: в проекте, где постоянно менялись требования к валидации, добавление и выключение отдельных валидаторов‑декораторов происходило просто пересборкой цепочек, без правок бизнес‑логики.
Обучение и ресурсы: где прокачать понимание Декоратора
Поскольку обучение JavaScript паттерны проектирования декоратор сейчас входит почти в любой продвинутый курс, найти материал не проблема. Но важно не зарыться в теории.
1. Начать хорошо с небольшой «книга по JavaScript паттерны проектирования декоратор», где есть и UML, и код, и живые примеры (Gang of Four в адаптированном для JS виде или современные книги по практическим паттернам).
2. Далее помогают практико‑ориентированные курсы по JavaScript паттерн декоратор онлайн, где решают реальные задачи: логирование, кэширование, адаптация API.
3. И обязательный этап — дописать свой маленький проект, где вы явно выделяете декораторы и заменяете ими «простыни» if-ов и копипасту.
Без практики паттерн остаётся только красивым словом.
Как выбирать подход: чек‑лист для разработчика
Наследование, функции или декораторы — что лучше?
Удобно держать в голове простой алгоритм:
1. Если это простая вариация поведения и у вас один-два сценария — можно обойтись наследованием или конфигами.
2. Если нужно гибко комбинировать независимые фичи (логирование, кэш, ретраи, метрики) — берите Декоратор.
3. Если вы в функциональном стиле и не держитесь за классы — делайте функции‑обёртки (higher-order functions).
4. Если пишете на TypeScript/современном JS и используете сборщик — присмотритесь к синтаксическим декораторам `@`.
Практические рекомендации
- Не начинайте с паттернов. Сначала пишите прямое решение; если видите дублирование и растущий комбайн — выделяйте Декоратор.
- Давайте декораторам говорящие имена: `LoggingPaymentClient`, `CachedUserService` — код читается как фраза.
- Не смешивайте в одном декораторе всё подряд — один аспект ответственности на один декоратор.
- Документируйте стандартные цепочки: «в этом проекте каждый HTTP‑клиент проходит через `withAuth` → `withLogging` → `withMetrics`». Это сильно снижает порог вхождения для новичков.
Тенденции 2025 года: куда движется Декоратор в JS‑мире

К 2025 году несколько трендов особенно заметны:
- Стандартизация синтаксиса декораторов. Появляется всё больше библиотек, использующих `@`‑декораторы поверх классов: для DI, валидации, API‑слоёв. Многие фреймворки уже предлагают официальные декораторы для контроллеров, сервисов и моделей.
- Смещение в функциональную сторону. В экосистеме React, Svelte, Node middleware всё чаще используют чистые функции как декораторы. Это проще для тестов и более прозрачно для анализа.
- Интеграция с метриками и observability. Декораторы становятся стандартным способом внедрения логов, метрик и трассировки в продакшен‑код без вмешательства в бизнес‑логику.
- Best practices вместо «магии». Разработчики всё чаще явно называют вещи своими именами: в коде и документации пишут «здесь используется паттерн Декоратор», а не прячут его под слоем «фреймворк всё делает сам».
В итоге Декоратор в JavaScript перестаёт быть «паттерном из учебника» и превращается в обычный рабочий инструмент, как промисы или middleware.
Итоги: когда Декоратор — ваш лучший друг
Если вы ловите себя на том, что:
- добавляете в класс всё новые флаги и режимы;
- копируете один и тот же код логирования/валидации/кэша в разные места;
- боитесь лезть в старый класс, потому что «сломается пол‑проекта»,
— это хороший момент остановиться и попробовать Декоратор.
Начните с маленького шага: выделите повторяющееся поведение в отдельный класс или функцию‑обёртку и «оберните» исходный объект. После пары таких рефакторингов паттерн становится естественным приёмом. А дальше любая книга по JavaScript паттерны проектирования декоратор или даже короткие видео начнут читаться гораздо проще — вы будете узнавать там свои собственные кейсы.
Главное — помнить: паттерн Декоратор — не про модные слова, а про простую идею «не трогай рабочий код, а аккуратно оберни его новым поведением». Именно за это его так ценят в современных JS‑проектах.



