Adapter vs Decorator vs Proxy vs Facade — Сравнение

Четыре структурных паттерна, которые в JS выглядят почти одинаково. Разница — в интенции, не в коде.

Главный тезис автора

«В JavaScript этим паттернам становятся очень похожи. В динамических языках они становятся иногда по коду совершенно неотличимы. Но они существенно отличимы по цели, по интенции своего существования.»

В C++/Java они различались по реализации (наследование, контракты, mixin). В JS — структурная композиция везде одинаковая.

Различающая таблица

Паттерн Сколько абстракций Зачем
Adapter 1 → 1 Стыковать две существующие абстракции с несовместимыми контрактами
Decorator 1 → 1 Добавить метаданные/поведение к одной абстракции, сохраняя её интерфейс
Proxy 1 → 1 Перехватить доступ для контроля, защиты, ленивых вычислений, профилирования
Facade N → 1 Упростить доступ к сложной подсистеме из нескольких классов
Composite 1 → дерево Единый интерфейс к дереву похожих узлов
Flyweight N эталонов + N инстансов Экономия памяти через общее состояние
Bridge 2 иерархии Разделить иерархии наследования, чтобы менять независимо

По коду они почти одинаковы

// Adapter
class FooAdapter {
  constructor(legacyFoo) { this.foo = legacyFoo; }
  newMethod { return this.foo.oldMethod; }
}

// Decorator
class LoggedFoo {
  constructor(foo) { this.foo = foo; }
  method(x) {
    console.log('call', x);
    return this.foo.method(x);
  }
}

// Proxy (классическая реализация — без new Proxy)
class FooProxy {
  constructor(foo) { this.foo = foo; }
  method(x) {
    if (!this.hasAccess) throw new Error('forbidden');
    return this.foo.method(x);
  }
}

// Facade
class FooFacade {
  #db; #cache; #logger;
  constructor { /* инициализация подсистемы */ }
  doStuff(x) { /* координирует #db, #cache, #logger */ }
}

Структурно — везде поле + делегация. Но зачем мы это делаем, разное:

  • Adapter: «контракты не совпадают»
  • Decorator: «нужны метаданные/побочное поведение»
  • Proxy: «нужен контроль доступа»
  • Facade: «много классов, нужен простой API»

Что часто путают

Adapter vs Decorator

  • Adapter изменяет интерфейс: legacyFoo.oldMethodadapter.newMethod
  • Decorator сохраняет интерфейс: foo.methoddecorated.method (только добавляет поведение)

Decorator vs Proxy

  • Decorator добавляет поведение/метаданные
  • Proxy контролирует доступ — может запретить вызов

Proxy vs Flyweight

  • Proxy перехватывает доступ (контроль)
  • Flyweight экономит память (производительность)

Facade vs Adapter

  • Adapter: 1 контракт → другой контракт (1:1)
  • Facade: много контрактов → 1 контракт (N:1)

Facade vs Composite

  • Composite — дерево однотипных узлов с общим интерфейсом
  • Facade — несвязанные классы за одним интерфейсом
  • Корень дерева Composite можно назвать Facade'ом

Bridge vs Mediator

  • Bridge знает об иерархиях наследования и связывает их по контракту
  • Mediator не знает об иерархиях, централизует связи между объектами

Главные тезисы автора

  • «В JS хватает обычной структурной композиции» — поэтому паттерны схлопываются.
  • «Структурные паттерны — это композиция классов»: ссылка на другой инстанс внутри.
  • Все можно реализовать в функциональном стиле через замыкание.
  • Различать паттерны по интенции, не по коду — главное правило.
  • Adapter = стыковка двух абстракций, которые не можем/не хотим переписать.
  • Decorator = добавление декларативного синтаксиса + метаданных.
  • Proxy в JS = встроенный механизм перехвата, чего классы не могут.
  • Facade = главный паттерн архитектурных границ.

🎓 Источники

См. также