Использовал ли паттерн Revealing Constructor в JavaScript/Node.js?

Ответ

Да, сталкивался с этим паттерном. Revealing Constructor позволяет создавать объекты, приватное состояние которых можно настроить только в момент создания, передав в конструктор функцию-исполнитель (executor). Это обеспечивает строгую инкапсуляцию.

Классический пример — реализация, похожая на нативный Promise:

class ImmutableLogger {
  #logs = []; // Приватное поле

  constructor(executor) {
    // Исполнитель получает контролируемый интерфейс для работы
    executor({
      log: (message) => {
        this.#logs.push(`${new Date().toISOString()}: ${message}`);
      },
      getLogs: () => [...this.#logs] // Возвращаем копию
    });
  }
}

// Использование:
const logger = new ImmutableLogger(({ log }) => {
  log('Application started');
  log('User logged in');
});
// Прямой доступ к `#logs` извне невозможен

Где это применяется:

  1. Встроенные API: Сам Promise использует этот паттерн — функции resolve и reject доступны только исполнителю, переданному в конструктор.
  2. Библиотеки: Некоторые библиотеки для создания безопасных или иммутабельных структур данных.
  3. Мои проекты: Использовал подобный подход при написании кастомного Event Emitter, где нужно было дать возможность инициализировать слушатели только один раз при создании.

Ответ 18+ 🔞

А, ну это ж классика, ёпта! С Revealing Constructor'ом сталкивался, да не раз. Паттерн тот ещё, хитрая жопа, но гениальная по своей сути. Суть в чём: ты создаёшь объект, а его приватные кишки настраиваешь только в момент рождения, через специальную функцию-исполнителя. Получается такая железобетонная инкапсуляция, что потом хоть обосрись — извне к состоянию не подлезешь.

Вот смотри, как это выглядит на практике, прям как с нативным Promise:

class ImmutableLogger {
  #logs = []; // Вот это приватное поле, мой друг. Снаружи него не видно, как собаке пятая нога.

  constructor(executor) {
    // А тут мы этой самой функции-исполнителю даём в лапы контролируемый интерфейс
    executor({
      log: (message) => {
        this.#logs.push(`${new Date().toISOString()}: ${message}`);
      },
      getLogs: () => [...this.#logs] // И копию отдаём, а не оригинал, чтоб не накосячили
    });
  }
}

// Используем:
const logger = new ImmutableLogger(({ log }) => {
  log('Application started');
  log('User logged in');
});
// И всё, приехали. Попробуй теперь до `#logs` добраться снаружи — нихуя не выйдет. Доверия к такому коду — ебать ноль, потому что его просто не сломаешь.

А где эта штука, блядь, применяется? Да везде, где нужна жёсткая защита:

  1. Встроенные API: Самый яркий пример — Promise. Функции resolve и reject доступны только тому, кто внутри конструктора, то есть исполнителю. Удобно, чёрт возьми.
  2. Библиотеки: Разные умные библиотеки для безопасных или неизменяемых структур данных очень любят этот подход. Пизда рулю, когда нужно гарантировать, что состояние не испортят.
  3. Мои проекты: Я как-то писал кастомный Event Emitter, и там нужно было, чтобы слушатели инициализировались строго один раз при создании. Вот тут Revealing Constructor и выручил — дал возможность настроить всё изнутри, а потом наглухо захлопнул дверь. Получилось овердохуища надёжно.