Как реализовать наследование классов в TypeScript?

Наследование в TypeScript является ключевым механизмом объектно-ориентированного программирования. Оно позволяет создавать новые классы на основе уже существующих, повторно использовать их код и расширять функциональность. Понимание того, как работает наследование в TypeScript, помогает строить удобную, масштабируемую и поддерживаемую архитектуру приложений.

Базовое наследование с помощью extends

Для реализации наследования используется ключевое слово extends. Класс-наследник получает доступ ко всем публичным и защищенным (protected) свойствам и методам родительского класса.

Пример:

class Animal {
constructor(public name: string) {}
move(): void {
console.log(\`${this.name} is moving\`);
}
}
class Dog extends Animal {
bark(): void {
console.log(\`${this.name} says: woof!\`);
}
}
const dog = new Dog("Rex");
dog.move(); // Rex is moving
dog.bark(); // Rex says: woof!

Здесь класс Dog наследует от Animal, автоматически получая доступ к методу move.

Конструкторы и super

Если в наследуемом классе есть свой конструктор, то внутри него необходимо вызвать super() для инициализации родительского класса. Это обязательное правило при использовании конструктора в дочернем классе.

Пример:

class Animal {
constructor(public name: string) {}
}
class Bird extends Animal {
constructor(name: string, public canFly: boolean) {
super(name); // вызов конструктора родителя
}
describe(): void {
console.log(\`${this.name} can fly: ${this.canFly}\`);
}
}
const parrot = new Bird("Parrot", true);
parrot.describe(); // Parrot can fly: true

Здесь ключевое слово super используется для передачи аргумента в конструктор родителя.

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

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

Пример:

class Animal {
move(): void {
console.log("Animal is moving");
}
}
class Cat extends Animal {
move(): void {
console.log("Cat is moving silently");
}
}
const cat = new Cat();
cat.move(); // Cat is moving silently

Таким образом, Cat изменяет реализацию метода move, не затрагивая сам класс Animal.

Вызов родительских методов через super

При переопределении метода есть возможность вызвать реализацию из родительского класса с помощью super.methodName(). Это позволяет не полностью заменять поведение, а лишь дополнять его.

Пример:

class Animal {
move(): void {
console.log("Animal is moving");
}
}
class Horse extends Animal {
move(): void {
super.move(); // вызов родительского метода
console.log("Horse is galloping");
}
}
const horse = new Horse();
horse.move();
// Animal is moving
// Horse is galloping

Использование модификаторов доступа

При наследовании стоит учитывать модификаторы доступа:

  • public — доступен везде, включая наследников.

  • protected — доступен в классе и его наследниках, но недоступен извне.

  • private — доступен только внутри самого класса и не наследуется.

Пример:

class Vehicle {
protected wheels: number;
constructor(wheels: number) {
this.wheels = wheels;
}
}
class Car extends Vehicle {
describe(): void {
console.log(\`This car has ${this.wheels} wheels\`);
}
}
const car = new Car(4);
car.describe(); // This car has 4 wheels

Наследование и абстрактные классы

Наследование тесно связано с абстрактными классами. Если родительский класс является абстрактным, дочерний обязан реализовать все абстрактные методы, чтобы можно было создавать его экземпляры.

Пример:

abstract class Shape {
abstract area(): number;
}
class Rectangle extends Shape {
constructor(private width: number, private height: number) {
super();
}
area(): number {
return this.width \* this.height;
}
}

В этом примере Rectangle обязан реализовать метод area, так как он был абстрактным в классе Shape.