类实现接口
本教程共 80 篇 · 第 36 篇 · 更新于 2026-08-10 · 约 10 分钟阅读
本节目标:掌握 implements 关键字的用法,理解”接口定义契约、类提供实现”的协作模式,能区分 extends 和 implements 的使用场景。
第 24 章学的接口可以定义对象形状,第 33 章学的继承可以让子类复用父类代码。在面向对象设计里,还有一个重要的关系:“这个类承诺提供某些能力”。这就是接口和类之间的 implements 关系。
implements:类兑现接口
interface Flyable {
fly(): void;
land(): void;
}
class Bird implements Flyable {
fly(): void {
console.log("鸟儿在飞");
}
land(): void {
console.log("鸟儿降落");
}
}
class Airplane implements Flyable {
fly(): void {
console.log("飞机起飞");
}
land(): void {
console.log("飞机着陆");
}
}
Bird implements Flyable 的意思是:“我承诺 Bird 类具备 Flyable 接口要求的所有属性和方法。“如果少了任何一个,TypeScript 马上报错:
class BrokenBird implements Flyable {
// ❌ Class 'BrokenBird' incorrectly implements interface 'Flyable'.
// Property 'fly' is missing in type 'BrokenBird'.
land(): void {
console.log("尝试降落");
}
}
接口就是一份合同。implements 是你在合同上签字——签了就得履行,缺一条都不行。
实现多个接口
一个类可以实现多个接口,用逗号分隔:
interface Flyable {
fly(): void;
}
interface Swimmable {
swim(): void;
}
interface Runnable {
run(): void;
}
class Duck implements Flyable, Swimmable, Runnable {
fly(): void {
console.log("鸭子飞起来了");
}
swim(): void {
console.log("鸭子在游泳");
}
run(): void {
console.log("鸭子在跑");
}
}
这弥补了 JavaScript / TypeScript 只支持单继承的局限——虽然你只能 extends 一个类,但可以 implements 任意多个接口。
接口定义契约,类提供实现
implements 的一个关键价值是解耦。消费代码只依赖接口,不依赖具体类:
interface Logger {
log(message: string): void;
error(message: string): void;
}
class ConsoleLogger implements Logger {
log(message: string): void {
console.log(`[INFO] ${message}`);
}
error(message: string): void {
console.error(`[ERROR] ${message}`);
}
}
class FileLogger implements Logger {
private filePath: string;
constructor(filePath: string) {
this.filePath = filePath;
}
log(message: string): void {
// 实际项目里会写文件,这里用输出模拟
console.log(`[FILE:${this.filePath}] [INFO] ${message}`);
}
error(message: string): void {
console.log(`[FILE:${this.filePath}] [ERROR] ${message}`);
}
}
// 函数只依赖 Logger 接口,不关心具体实现
function doWork(logger: Logger): void {
logger.log("开始执行任务");
// ... 执行任务 ...
logger.log("任务完成");
}
doWork(new ConsoleLogger());
doWork(new FileLogger("app.log"));
将来你想加一个 CloudLogger,只要它 implements Logger,doWork() 函数一行都不用改。这就是面向接口编程的好处。
implements 不会改变类的类型
这是一个经典的误解:以为 implements 会影响类的类型推断。它不会。
interface Named {
name: string;
}
class Person implements Named {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
const p = new Person("Alice", 30);
// p 的类型是 Person,不是 Named
console.log(p.age); // ✅ 可以访问 age,因为类型是 Person
implements 只是检查”类是否满足接口”,不会改变类本身的类型。这和第 24 章接口的 extends 不一样——interface A extends B 会合并类型。
另一个容易踩的坑:implements 不会影响类内部方法的参数类型推断:
interface Checkable {
check(name: string): boolean;
}
class NameChecker implements Checkable {
check(s) { // s 的类型是 any,不是 string!
// 有人的预期是 s 的类型会从接口中推断为 string,但不会
return s.toLowerCase() === "ok";
}
}
类内部方法的参数类型,还是得自己在类里写。接口不会”渗透”进去。
implements 可选属性不会自动创建
接口里的可选属性,implements 之后不会自动出现在类上:
interface Config {
url: string;
timeout?: number;
}
class AppConfig implements Config {
url = "https://api.example.com";
// timeout 是可选属性,可以不写
}
const config = new AppConfig();
// config.timeout = 5000; // ❌ AppConfig 类型上没有 timeout 属性
如果你想在实例上访问 timeout,得在类里显式声明。但在结构类型系统里,用接口类型来接收就没问题:
const config: Config = new AppConfig();
config.timeout = 5000; // ✅ 通过 Config 类型访问,没问题
implements vs extends
| 维度 | extends(类继承) | implements(接口实现) |
|---|---|---|
| 关键词 | ”是一个" | "能做” |
| 继承/实现数量 | 只能一个 | 可以多个 |
| 共享代码 | ✅ 继承父类的属性和方法 | ❌ 只约束类型,不给代码 |
| 父/接口能否实例化 | 可以(除非抽象类) | 不能 |
| 编译后 | 保留继承关系 | 完全抹除 |
| 典型场景 | 子类是父类的特化版本 | 不相关的类共享同一套能力 |
一句话总结:extends 拿代码,implements 拿契约。
用现实世界类比:Dog extends Animal 是说”狗是一种动物”(is-a)。Dog implements Runnable 是说”狗具备跑的能力”(can-do)。
同时使用 extends 和 implements
一个类可以同时继承一个父类并实现多个接口:
interface Flyable {
fly(): void;
}
interface Swimmable {
swim(): void;
}
class Animal {
constructor(public name: string) {}
breathe(): void {
console.log(`${this.name} 在呼吸`);
}
}
class Duck extends Animal implements Flyable, Swimmable {
fly(): void {
console.log(`${this.name} 飞起来`);
}
swim(): void {
console.log(`${this.name} 在游泳`);
}
}
const duck = new Duck("唐老鸭");
duck.breathe(); // 从 Animal 继承
duck.fly(); // 实现 Flyable
duck.swim(); // 实现 Swimmable
这里的 Duck 既继承了 Animal 的全部代码(name、breathe()),又兑现了 Flyable 和 Swimmable 的契约。
Tip在设计类的时候,建议先想清楚”这个类是什么”(继承关系)和”这个类能做什么”(实现的接口)。把”是什么”用
extends串起来,把”能做什么”用implements列出来。这样设计出来的类层次更清晰。
接口继承接口 + 类实现接口
接口之间也可以用 extends 继承,这是和第 25 章讲的一样。然后类再 implements 最终的接口:
interface Readable {
read(): string;
}
interface Writable {
write(data: string): void;
}
interface ReadWrite extends Readable, Writable {
// 继承了 read() 和 write()
}
class FileHandler implements ReadWrite {
private content = "";
read(): string {
return this.content;
}
write(data: string): void {
this.content += data;
}
}
这种模式在大型项目里很常见——顶层接口定义能力原子,中间接口组合能力,类最后实现。
小结
implements让类兑现接口契约,缺少成员会报错。- 一个类可以实现多个接口,弥补了单继承的限制。
implements只做类型检查,不影响类的类型推断和内部类型。- 接口的可选属性不会自动添加到类上。
extends拿代码(is-a),implements拿契约(can-do)。- 一个类可以同时
extends一个父类并implements多个接口。
到目前为止,我们学的都是单继承体系。但现实中有时候一个类需要从多个来源组合行为——日志、序列化、缓存、时间戳等等。单继承做不到,接口又只能定义签名不能给代码。有没有办法两全其美?有——这就是下一章的 Mixin 混入模式。