Revealing Constructor — Открытый конструктор

Передача функции в конструктор, которая получает «инструменты» внутреннего состояния. Не из GoF, но в JS — повсеместно. Канонический пример: new Promise((resolve, reject) => ...).

Проблема

Хочется передать в объект поведение, не наследуя от него и не подменяя методы. При этом поведение должно иметь доступ к внутренним «рычагам» объекта (как resolve/reject у промиса) — но эти рычаги не должны быть публичными.

Решение

  • Конструктор принимает функцию-callback.
  • Функция получает внутренние controls объекта как аргументы.
  • Controls не доступны снаружи объекта — только в этом callback'е.

Пример в JS

// Промис — каноничный revealing constructor
const p = new Promise((resolve, reject) => {
  setTimeout( => resolve(42), 1000);
});
// resolve/reject не доступны снаружи — только через .then/.catch

// Transform stream
const transform = new Transform({
  transform(chunk, enc, cb) { cb(null, chunk.toUpperCase()); },
});

// Свой Future без состояния
class Future {
  constructor(executor) {
    this.executor = executor; // не вызываем сразу
  }
  subscribe(observer) {
    this.executor(observer);   // вызываем при подписке
  }
}

const f = new Future((emit) => {
  let i = 0;
  setInterval( => emit(i++), 1000);
});
f.subscribe(console.log);

Где используется в JS-экосистеме

  • new Promise(executor) — главный пример
  • new TransformStream({ transform }) — Web/Node Streams
  • new ReadableStream({ start, pull, cancel }) — Streams API
  • Array.from({ length: 5 }, (_, i) => i) — фабрика-функция в конструктор
  • new Proxy(target, handler) — handler как открытый конструктор
  • MobX observable(obj, { ... }) — конфигурация через объект-функцию

Подводные камни

  • Не путать с Builder: Builder возвращает себя, revealing constructor принимает executor.
  • Не путать с Strategy: Strategy — выбор алгоритма; revealing — инжекция поведения с доступом к внутреннему API.
  • Executor вызывается в конструкторе — может стрелять до завершения инициализации (если до this.x = ...).
  • В Promise executor вызывается сразу синхронно — это часто сюрприз.

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

  • «В банде 4 его нет, но он очень типичен для JS-мира».
  • «Позволяет без наследования переопределять поведение».
  • «TransformStream — самый простой пример»: функция трансформации в конструктор.
  • Работает благодаря функциям первого класса в JS.
  • Future без состояния и многоразовый: можно композировать, в отличие от Promise.
  • Промис = revealing constructor: callback с resolve/reject переводит между состояниями.
  • Три способа передачи значения: константа, функция-геттер, функция с callback — третий и есть revealing constructor.

🎓 Источники

См. также