在 TypeScript 中,抽象类 和 接口 都是面向对象编程的重要特性,用于定义对象的结构和行为,但它们的目的和使用方式不同。以下从多个维度对抽象类和接口的区别进行详细说明,并给出示例。
1. 定义的目的
特性 | 抽象类 | 接口 |
---|---|---|
用途 | 描述一类对象的公共特性,并允许包含具体实现的成员。 | 描述对象的结构和契约,仅定义需要实现的行为和属性。 |
设计场景 | 用于表示“是什么”(一种具体的抽象概念)。 | 用于表示“能做什么”(一种能力或功能)。 |
示例:
abstract class Animal {
abstract makeSound(): void; // 抽象方法,无实现
move(): void {
console.log("Moving...");
}
}
interface Flyable {
fly(): void; // 接口方法,不能包含实现
}
2. 语法结构
特性 | 抽象类 | 接口 |
---|---|---|
实现方法 | 可以包含抽象方法和具体方法。 | 只能包含方法声明,不能有具体实现。 |
属性 | 可以包含字段(包括初始化值)。 | 只能定义字段类型,不能包含实现。 |
访问修饰符 | 支持 public 、private 、protected 。 | 不支持访问修饰符,所有成员默认是 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. 总结对比
特性 | 抽象类 | 接口 |
---|---|---|
是否包含实现 | 可以包含抽象方法和具体实现。 | 只能包含方法签名和字段类型。 |
继承关系 | 支持单继承。 | 支持多继承(类可实现多个接口)。 |
访问修饰符 | 支持 public 、private 、protected 。 | 不支持访问修饰符,成员默认是 public 。 |
运行时功能 | 存在运行时行为,例如属性初始化、方法实现。 | 仅限编译时检查,运行时无实际作用。 |
代码复用 | 可以通过继承复用代码。 | 无法实现代码复用。 |
使用场景 | 表示对象的具体类别和部分实现。 | 表示对象的能力或契约,适合组合多个功能。 |
简单来说:
- 抽象类:用来定义“是什么”(具体的抽象概念),适合有部分实现且需要运行时功能的场景。
- 接口:用来定义“能做什么”(能力或契约),适合多功能组合或仅描述对象结构的场景。