PassFront
  1. PassFront
  2. База знаний
  3. Смежные принципы проектирования

Почему часто советуют предпочитать композицию наследованию?

Смежные принципы проектирования

Наследование — самая сильная связь между классами: наследник зависит от деталей реализации родителя. Композиция собирает поведение из независимых частей, которые меняются и комбинируются даже во время выполнения.

Проблемы наследования

  • Хрупкий базовый класс: безобидная правка родителя ломает наследников. Пример Блоха из «Effective Java»: счётчик в наследнике HashSet переопределяет add и addAll, а родительский addAll сам вызывает add — элементы считаются дважды.
  • Комбинаторный взрыв: LoggedCachedRepository, CachedLoggedRepository — иерархия растёт как произведение осей вариативности.
  • Одиночное наследование в TS и Java: поведение из двух веток не взять.
  • Иерархия фиксируется при компиляции.

Композиция

/** Контракты: Repo — { get(id): Promise<item | undefined> }, Logger — { log(msg) } */
class LoggedRepo {
  #inner;
  #logger;
  constructor(inner, logger) {
    this.#inner = inner;
    this.#logger = logger;
  }
  async get(id) {
    this.#logger.log(`get ${id}`);
    return this.#inner.get(id);
  }
}

Декоратор оборачивает любой Repo: кэширование и логирование комбинируются в любом порядке без новых классов.

Когда наследование уместно

  • Настоящее «является» с соблюдением LSP.
  • Этого требует платформа: extends Error, HTMLElement в Web Components.
  • В React документация прямо советует композицию: для иерархий компонентов в Facebook не нашли случаев применения.

Коротко для собеседования

Наследник привязан к деталям родителя, поэтому иерархии хрупки и разрастаются комбинаторно. Композиция и декораторы собирают поведение из независимых частей в любом сочетании, а наследование оставляют для настоящего «является» с соблюдением LSP или для требований платформы.

Другие вопросы раздела