TypeScript 抽象类和接口的区别

34 阅读4分钟

在 TypeScript 中,抽象类接口 都是面向对象编程的重要特性,用于定义对象的结构和行为,但它们的目的和使用方式不同。以下从多个维度对抽象类和接口的区别进行详细说明,并给出示例。


1. 定义的目的

特性抽象类接口
用途描述一类对象的公共特性,并允许包含具体实现的成员。描述对象的结构和契约,仅定义需要实现的行为和属性。
设计场景用于表示“是什么”(一种具体的抽象概念)。用于表示“能做什么”(一种能力或功能)。

示例:

abstract class Animal {
  abstract makeSound(): void; // 抽象方法,无实现
  move(): void {
    console.log("Moving...");
  }
}

interface Flyable {
  fly(): void; // 接口方法,不能包含实现
}

2. 语法结构

特性抽象类接口
实现方法可以包含抽象方法具体方法只能包含方法声明,不能有具体实现。
属性可以包含字段(包括初始化值)。只能定义字段类型,不能包含实现。
访问修饰符支持 publicprivateprotected不支持访问修饰符,所有成员默认是 public

抽象类示例:

abstract class Animal {
  abstract makeSound(): void; // 抽象方法
  move(): void {
    console.log("Moving...");
  }
}

class Dog extends Animal {
  makeSound(): void {
    console.log("Bark");
  }
}

接口示例:

interface Flyable {
  fly(): void;
}

class Bird implements Flyable {
  fly(): void {
    console.log("Flying...");
  }
}

3. 继承和实现

特性抽象类接口
继承关系一个类只能继承一个抽象类(单继承)。一个类可以实现多个接口(多实现)。
扩展性抽象类可以通过继承扩展功能。接口可以通过 extends 扩展其他接口的功能。

抽象类继承:

abstract class Animal {
  abstract makeSound(): void;
}

abstract class Mammal extends Animal {
  abstract walk(): void;
}

class Dog extends Mammal {
  makeSound(): void {
    console.log("Bark");
  }
  walk(): void {
    console.log("Walking...");
  }
}

接口继承与实现:

interface Flyable {
  fly(): void;
}

interface Swimmable {
  swim(): void;
}

class Duck implements Flyable, Swimmable {
  fly(): void {
    console.log("Flying...");
  }
  swim(): void {
    console.log("Swimming...");
  }
}

4. 运行时与编译时

特性抽象类接口
运行时表现是 JavaScript 中实际存在的构造函数(类)。仅存在于 TypeScript 编译期,运行时会被移除。
用途提供运行时功能支持(如属性初始化、方法实现)。仅用于类型检查,无法提供运行时功能。

抽象类运行时:

abstract class Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

class Dog extends Animal {
  constructor(name: string) {
    super(name);
  }
}

const dog = new Dog("Buddy");
console.log(dog.name); // "Buddy"

接口编译后移除:

interface Flyable {
  fly(): void;
}

class Bird implements Flyable {
  fly(): void {
    console.log("Flying...");
  }
}

const bird = new Bird();
bird.fly(); // "Flying..."

编译后的 JavaScript:

class Bird {
  fly() {
    console.log("Flying...");
  }
}

const bird = new Bird();
bird.fly();

5. 使用场景对比

场景适合抽象类适合接口
定义一类对象的基本行为需要一些具体实现时,例如公共方法或初始化逻辑。只需要定义类型或约束行为,不需要实现时。
代码复用子类可以继承抽象类的实现,复用具体方法和属性。接口无法实现代码复用,只能作为契约约束。
多功能组合(多继承)不支持多个抽象类继承。支持一个类实现多个接口,适合混合功能的场景。
运行时功能需要在运行时检查或使用时,例如基类逻辑。不需要运行时功能,仅用于类型检查。

6. 两者结合使用

在复杂场景中,抽象类和接口可以配合使用。

示例:

interface Flyable {
  fly(): void;
}

interface Swimmable {
  swim(): void;
}

abstract class Animal {
  abstract makeSound(): void;

  move(): void {
    console.log("Moving...");
  }
}

class Duck extends Animal implements Flyable, Swimmable {
  makeSound(): void {
    console.log("Quack");
  }

  fly(): void {
    console.log("Flying...");
  }

  swim(): void {
    console.log("Swimming...");
  }
}

const duck = new Duck();
duck.makeSound(); // "Quack"
duck.fly();       // "Flying..."
duck.swim();      // "Swimming..."

7. 总结对比

特性抽象类接口
是否包含实现可以包含抽象方法和具体实现。只能包含方法签名和字段类型。
继承关系支持单继承。支持多继承(类可实现多个接口)。
访问修饰符支持 publicprivateprotected不支持访问修饰符,成员默认是 public
运行时功能存在运行时行为,例如属性初始化、方法实现。仅限编译时检查,运行时无实际作用。
代码复用可以通过继承复用代码。无法实现代码复用。
使用场景表示对象的具体类别和部分实现。表示对象的能力或契约,适合组合多个功能。

简单来说:

  • 抽象类:用来定义“是什么”(具体的抽象概念),适合有部分实现且需要运行时功能的场景。
  • 接口:用来定义“能做什么”(能力或契约),适合多功能组合或仅描述对象结构的场景。