Наследование классов

Наследование классов

Технический фундамент прототипной цепочки

Наследование в JS строится через цепочку прототипов:

  • методы дочернего класса ищутся сначала в нем, затем в родителе;
  • extends связывает прототипы автоматически;
  • super(...) и super.method() дают доступ к родительской логике;
  • при override нужно сохранять ожидаемый контракт базового метода.

Из-за этого важно проектировать базовый класс как устойчивый контракт, а не как "свалку общей логики".

Зачем нужно наследование

В проекте часто есть сущности, у которых много общего, но есть и отличия. Вместо копирования одного и того же кода в разные классы можно выделить базовый класс и расширять его через наследование.

Ключевой момент: наследование позволяет переиспользовать общую логику и добавлять специализированное поведение в дочерних классах.

Проверь себя: почему копипаст одинаковых методов в 5 классах повышает риск багов при изменениях?

Базовый синтаксис extends и super

class User {
  constructor(name) {
    this.name = name;
  }

  getLabel() {
    return `User: ${this.name}`;
  }
}

class Admin extends User {
  constructor(name, level) {
    super(name);
    this.level = level;
  }
}

const admin = new Admin('Оля', 2);
console.log(admin.getLabel());

Что важно:

  • extends связывает дочерний класс с базовым;
  • super(...) в конструкторе вызывает конструктор родителя;
  • в дочернем классе super(...) нужно вызвать до this.

Здесь часто путаются: если в дочернем классе есть constructor, но ты не вызвал super, будет ошибка.

Переопределение методов (override)

Дочерний класс может изменить поведение метода родителя.

class User {
  getRole() {
    return 'user';
  }
}

class Admin extends User {
  getRole() {
    return 'admin';
  }
}

Дополнительный пример: расширение родительского метода через super.

class User {
  getPermissions() {
    return ['read'];
  }
}

class Admin extends User {
  getPermissions() {
    return [...super.getPermissions(), 'write', 'delete'];
  }
}

Это называется переопределением метода.

Можно также использовать super.methodName() внутри переопределенного метода, чтобы расширить базовое поведение, а не полностью заменить его.

Реальные микро-сценарии

  1. Пользователи в системе доступа.
  • User - базовые свойства и методы.
  • Admin - дополнительные права.
  • Moderator - специфичные действия модерации.
  1. Платежные методы.
  • PaymentMethod - общий интерфейс оплаты.
  • CardPayment, CryptoPayment - разные реализации с общей базой.
  1. UI-компоненты.
  • BaseComponent с общими mount/unmount.
  • Modal, Tooltip, Dropdown расширяют базовый класс.

Смотри, что важно: наследование полезно, когда есть реальное отношение "является" (Admin является User).

Когда наследование превращается в проблему

Слишком глубокая иерархия (A -> B -> C -> D...) усложняет понимание и отладку. Изменение в базовом классе может неожиданно повлиять на множество потомков.

Анти-провал:

  • не делать иерархию ради иерархии;
  • избегать слишком универсальных "бог-классов" в основании;
  • для части задач использовать композицию (сборку из отдельных объектов/функций), если это проще.

Проверь себя: если классы не имеют естественного отношения "is-a", стоит ли их связывать через extends?

Частые ошибки новичков

  • Забывать super(...) в конструкторе наследника.
  • Переопределять метод и ломать ожидаемый контракт (другой тип результата).
  • Наследоваться только ради "экономии строк", не думая о модели предметной области.
  • Добавлять слишком много логики в базовый класс, делая его хрупким.

Что будет, если изменить входные данные

Если в new Admin(name, level) передать пустое name, а валидации нет, ошибка проявится позже в разных методах. Поэтому базовый класс должен задавать устойчивый контракт и проверять критичные поля.

Проверь себя: где логичнее проверять имя - в базовом User или отдельно в каждом наследнике?

Краткий итог

  • Наследование (extends) позволяет переиспользовать общую логику базового класса.
  • super нужен для вызова логики родителя, особенно в конструкторе.
  • Переопределение методов дает специализированное поведение для дочерних классов.
  • Наследование полезно только при естественной связи "является".
  • Слишком глубокие иерархии усложняют код, поэтому используй наследование осознанно.