extends и implements в TypeScript: в чем разница?
Ключевая разница: наследование реализации vs. следование контракту
Давайте разберем фундаментальное отличие, которое сразу всё прояснит:
- extends — это наследование. Вы берете готовый класс-родитель и получаете от него всю его реализацию (методы, свойства, логику). Вы можете переопределить (override) методы родителя или добавить свои.
- implements — это соблюдение контракта. Вы говорите TypeScript: "Этот класс обязуется иметь такую же структуру, как указанный интерфейс". Но никакой реализации вы не получаете — вы должны написать её сами.
Пример, показывающий разницу на практике
// Интерфейс — только описание структуры
interface Flyable {
fly(): void;
altitude: number;
}
// Класс с готовой реализацией
class Bird {
public name: string;
constructor(name: string) {
this.name = name;
}
public eat(): void {
console.log(`${this.name} is eating`);
}
}
// Вариант 1: implements — берём только "форму", пишем всё сами
class Airplane implements Flyable {
altitude: number = 0;
fly(): void {
console.log("Airplane is flying using engines");
}
}
// Вариант 2: extends — берём готовую реализацию + можем добавить
class Sparrow extends Bird implements Flyable {
altitude: number = 0;
constructor(name: string) {
super(name); // обязательно вызываем конструктор родителя
}
fly(): void {
console.log(`${this.name} is flying with wings`);
}
// Метод eat() уже есть от Bird, его не нужно писать заново
}
// Использование
const plane = new Airplane();
plane.fly(); // "Airplane is flying using engines"
const sparrow = new Sparrow("Jack");
sparrow.eat(); // "Jack is eating" — метод унаследован от Bird
sparrow.fly(); // "Jack is flying with wings" — своя реализация
Когда что использовать?
- extends — когда у вас есть готовый класс с логикой, которую вы хотите переиспользовать. Например, базовый класс
Vehicleс методамиstart(),stop(), и вы создаетеCar extends Vehicle. - implements — когда вы хотите гарантировать, что класс соответствует определённой структуре (интерфейсу), но реализация может быть совершенно разной. Например, интерфейс
Serializableс методомtoJSON()— разные классы могут сериализоваться по-разному.
Комбинирование extends и implements
Вы можете использовать оба ключевых слова одновременно! Это очень мощный паттерн:
interface IEmployee {
salary: number;
work(): void;
}
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
introduce(): void {
console.log(`Hi, I'm ${this.name}`);
}
}
// Наследуем реализацию Person + соблюдаем контракт IEmployee
class Developer extends Person implements IEmployee {
salary: number;
constructor(name: string, age: number, salary: number) {
super(name, age); // вызываем конструктор Person
this.salary = salary;
}
work(): void {
console.log(`${this.name} is writing code`);
}
// introduce() уже есть от Person
}
Важное замечание про интерфейсы
Интерфейсы тоже могут расширять другие интерфейсы (через extends):
interface BasicAnimal {
name: string;
makeSound(): void;
}
interface Pet extends BasicAnimal {
owner: string;
play(): void;
}
// Класс должен реализовать ВСЕ поля из BasicAnimal и Pet
class Cat implements Pet {
name: string;
owner: string;
constructor(name: string, owner: string) {
this.name = name;
this.owner = owner;
}
makeSound(): void {
console.log("Meow!");
}
play(): void {
console.log(`${this.name} is playing`);
}
}
Итог: extends = "я беру твою реализацию", implements = "я обязуюсь иметь такую же форму". Используйте их осознанно, и код станет чище и понятнее.
Дополнение: extends для интерфейсов и абстрактные классы
Стоит упомянуть, что extends может использоваться не только с классами, но и с интерфейсами. Это позволяет создавать иерархии типов:
interface Shape {
color: string;
}
interface Square extends Shape {
sideLength: number;
}
// Теперь Square включает color + sideLength
Также есть третий вариант — абстрактные классы (abstract class). Они занимают промежуточное положение:
- Могут содержать как реализованные методы (как обычный класс), так и абстрактные (без реализации, как интерфейс).
- Используются с
extends, но сами по себе не могут быть инстанциированы.
abstract class Vehicle {
abstract start(): void; // без реализации
stop(): void { // с реализацией
console.log("Vehicle stopped");
}
}
class Car extends Vehicle {
start(): void { // обязаны реализовать
console.log("Car started");
}
// stop() уже есть
}
Выбор между интерфейсом и абстрактным классом — вопрос дизайна. Если вам нужно только описание структуры — используйте interface. Если нужна частичная реализация — abstract class.
Ответов пока нет.
Войдите чтобы оставить ответ