interface 是纯契约,抽象类是"部分实现 + 部分契约"。implements 只检查形状,不继承任何实现。这三者的分工决定了代码结构。
三者对比
1// 抽象类与 interface 的分工
2
3// ---- interface:纯类型契约,无实现 ----
4interface Serializable {
5 serialize(): string;
6}
7
8// interface 可以有可选成员和只读成员
9interface Config {
10 readonly name: string;
11 debug?: boolean;
12}
13
14// ---- 抽象类:部分实现 + 强制子类实现部分 ----
15abstract class Shape {
16 // 抽象成员:子类必须实现
17 abstract area(): number;
18 abstract readonly kind: string;
19
20 // 已实现成员:子类继承
21 describe(): string {
22 return `${this.kind}: ${this.area().toFixed(2)}`;
23 }
24
25 // 模板方法:定义流程,子类填细节
26 report(): string {
27 return `[${this.describe()}]`;
28 }
29
30 // 抽象类可以有构造函数和状态
31 protected label: string;
32 constructor(label: string) {
33 this.label = label;
34 }
35}
36
37class Circle extends Shape {
38 readonly kind = "circle";
39 constructor(private radius: number) { super("circle"); }
40 area(): number { return Math.PI * this.radius ** 2; }
41}
42
43// ---- 一个类可以 implements 多个 interface,但只能 extends 一个类 ----
44interface Named { name: string }
45interface Aged { age: number }
46
47class Person implements Named, Aged {
48 constructor(public name: string, public age: number) {}
49}
50
51// ---- implements 只检查形状,不继承实现 ----
52class Version implements Serializable {
53 constructor(private major: number) {}
54 serialize(): string { return String(this.major); }
55}
56// 必须自己实现 serialize,implements 不给任何实现
57
58// ---- 抽象类也可以 implements interface ----
59abstract class BaseEntity implements Serializable {
60 abstract serialize(): string; // 声明为抽象,交给子类
61 id = 0;
62}
63
64class UserEntity extends BaseEntity {
65 serialize(): string { return JSON.stringify({ id: this.id }); }
66}
67
68// ---- 抽象类的实例侧与静态侧 ----
69abstract class Factory {
70 abstract create(): string;
71 static registry: string[] = [];
72}
73// 静态成员可以正常访问
74Factory.registry.push("a");
75// @ts-expect-error TS2511: Cannot create an instance of an abstract class.
76new Factory();
77
78// ---- 选择标准 ----
79// 用 interface:只要契约,多个类要共享形状,需要多实现
80// 用抽象类:需要共享实现、需要 protected 成员、需要模板方法
81
82// ---- 混合:抽象类 + interface 组合 ----
83interface Drawable { draw(): string }
84abstract class Renderer {
85 abstract render(target: Drawable): string;
86 log(msg: string): string { return `[log] ${msg}`; }
87}
88class SvgRenderer extends Renderer {
89 render(target: Drawable): string {
90 return this.log(target.draw());
91 }
92}
93
94// ---- 抽象类的 protected 成员不能被外部访问 ----
95class Sub extends Shape {
96 readonly kind = "sub";
97 area(): number { return 1; }
98 readLabel(): string { return this.label; } // ✓ protected 可被子类访问
99}
100
101const shapes: Shape[] = [new Circle(1), new Sub("s")];
102console.log(shapes.map((s) => s.report()));
103console.log(new Person("a", 1), new Version(1).serialize(), new UserEntity().serialize());
104console.log(new SvgRenderer().render({ draw: () => "svg" }), new Sub("x").readLabel());
105console.log(Factory.registry);
1[ '[circle: 3.14]', '[sub: 1.00]' ]
2Person { name: 'a', age: 1 } 1 {"id":0}
3[log] svg x
4[ 'a' ]
interface | 抽象类 | 普通类 | |
|---|---|---|---|
| 有实现 | ✗ | 部分 | ✓ |
| 有状态(字段) | ✗ | ✓ | ✓ |
| 能实例化 | ✗ | ✗ | ✓ |
extends 数量 | 多个(extends A, B) | 1 个 | 1 个 |
implements 数量 | — | 多个 | 多个 |
| 访问修饰符 | ✗ | ✓(protected 等) | ✓ |
抽象类的两个作用
作用一:不能实例化
1abstract class Factory {
2 abstract create(): string;
3}
4
5new Factory();
6// error TS2511: Cannot create an instance of an abstract class.
作用二:模板方法模式
父类定义流程,子类只填细节:
1abstract class Shape {
2 abstract area(): number;
3 abstract readonly kind: string;
4
5 // 已实现:子类继承
6 describe(): string {
7 return `${this.kind}: ${this.area().toFixed(2)}`;
8 }
9
10 // 模板方法:定义流程,调用抽象成员
11 report(): string {
12 return `[${this.describe()}]`;
13 }
14}
report() 调用了 describe(),describe() 调用了抽象的 area()。子类只需实现 area() 和 kind,就自动获得了完整的 report() 能力。
implements 只检查形状
1interface Serializable {
2 serialize(): string;
3}
4
5class Version implements Serializable {
6 serialize(): string { return String(this.major); } // 必须自己实现
7}
implements 不提供任何实现,它只是"编译期承诺这个类满足这个接口"。
数量限制
1// ✓ 可以实现多个接口
2class Person implements Named, Aged {
3 constructor(public name: string, public age: number) {}
4}
5
6// ✓ 抽象类也可以 implements
7abstract class BaseEntity implements Serializable {
8 abstract serialize(): string; // 声明为抽象,交给子类
9 id = 0;
10}
11
12// ✗ 只能继承一个类
13// class X extends A, B {}
interface 之间可以多继承:
1interface Combined extends Named, Aged {}
抽象类与 protected
抽象类独有的能力:protected 成员能被继承但对外不可见。
1abstract class Shape {
2 protected label: string;
3 constructor(label: string) { this.label = label; }
4}
5
6class Sub extends Shape {
7 readLabel(): string { return this.label; } // ✓ 子类可访问
8}
9
10new Sub("x").label;
11// error TS2445: Property 'label' is protected and only accessible within class
12// 'Shape' and its subclasses.
这是 interface 做不到的——接口只有公开成员。
选择标准
| 需求 | 用什么 |
|---|---|
| 只要契约 | interface |
| 多个不相关的类共享形状 | interface |
| 需要共享实现 | 抽象类 |
需要 protected 成员 | 抽象类 |
| 需要模板方法模式 | 抽象类 |
| 需要状态(字段) | 抽象类 |
实践建议
优先用 interface。它的约束更弱、组合更灵活(一个类可以实现多个接口),也避免了继承层级过深的问题。
只有在"确实要共享实现"或"需要 protected"时才用抽象类。这是"组合优于继承"原则在 TypeScript 里的体现。
混合使用
两者可以配合:
1interface Drawable { draw(): string }
2
3abstract class Renderer {
4 abstract render(target: Drawable): string;
5 log(msg: string): string { return `[log] ${msg}`; }
6}
7
8class SvgRenderer extends Renderer {
9 render(target: Drawable): string {
10 return this.log(target.draw()); // 复用了父类的 log
11 }
12}
Drawable 是契约(很多不相关的类都可以实现),Renderer 提供共享实现(log)。
相关
- 类与装饰器 —— 类的成员与修饰符
- interface 与 type —— 接口的选择
- 类型收窄 —— 用
instanceof收窄类实例