第 3 章:OOP 之魂 —— 何时用 interface
在前两章中,我们了解了 interface 和 type 的本质区别。今天我们来深入探讨在面向对象编程(OOP)中,如何恰当地使用 interface。
类的契约:implements
在面向对象编程中,接口最重要的作用之一就是定义类的契约。通过 implements 关键字,我们可以确保类实现了特定的方法和属性:
interface Flyable {
fly(): void;
}
interface Swimmable {
swim(): void;
}
class Duck implements Flyable, Swimmable {
fly() {
console.log("Duck is flying");
}
swim() {
console.log("Duck is swimming");
}
}这种方式有助于实现松耦合的设计,不同的类可以通过实现相同的接口来保证具备相同的行为能力。
继承体系:extends
在复杂的面向对象系统中,接口之间也可以建立继承关系,形成更加清晰的类型层次结构:
interface Animal {
name: string;
eat(): void;
}
interface Mammal extends Animal {
warmBlooded: boolean;
giveBirth(): void;
}
interface Dog extends Mammal {
breed: string;
bark(): void;
}
class Labrador implements Dog {
name: string = "Labrador";
warmBlooded: boolean = true;
breed: string = "Labrador Retriever";
eat() {
console.log("Dog is eating");
}
giveBirth() {
console.log("Dog gives birth to puppies");
}
bark() {
console.log("Woof! Woof!");
}
}通过接口继承,我们可以构建清晰的类型层次结构,每个层级都有明确的职责。
插件系统:声明合并
interface 的声明合并特性在构建插件系统时非常有用。许多流行的库如 Redux、Express 等都利用了这一特性:
// Redux 中间件的例子
interface MiddlewareAPI<S> {
getState(): S;
dispatch(action: any): any;
}
interface MiddlewareAPI<S> {
// 插件可以扩展这个接口
subscribe(listener: () => void): () => void;
}这种机制允许第三方库或应用程序的不同模块扩展核心接口,而无需修改原始定义。
适用场景:大型类库、企业级 OOP 架构
在以下场景中,推荐使用 interface:
1. 大型类库开发
当你开发一个供他人使用的类库时,使用 interface 可以提供更好的扩展性和兼容性:
// 类库定义的核心接口
interface DataProcessor<T> {
process(data: T): Promise<T>;
}
// 用户可以在自己的代码中扩展
interface DataProcessor<T> {
validate?(data: T): boolean;
}2. 企业级架构设计
在复杂的企业级应用中,接口可以帮助建立清晰的架构边界:
interface UserRepository {
findById(id: number): Promise<User>;
save(user: User): Promise<void>;
}
interface PaymentService {
processPayment(amount: number, userId: number): Promise<boolean>;
}
class DatabaseUserRepository implements UserRepository {
async findById(id: number): Promise<User> {
// 数据库查询实现
}
async save(user: User): Promise<void> {
// 数据库存储实现
}
}这种设计遵循了依赖倒置原则(Dependency Inversion Principle),高层模块不依赖于低层模块的具体实现,而是依赖于抽象接口。
与抽象类的区别
TypeScript 中既有 interface 也有抽象类(abstract class),它们都可以用于定义契约,但有不同的适用场景:
// 使用 interface
interface Shape {
getArea(): number;
}
// 使用 abstract class
abstract class AbstractShape {
abstract getArea(): number;
// 抽象类可以包含具体实现
toString() {
return `Area: ${this.getArea()}`;
}
}一般来说:
- 当只需要定义契约而不需要共享实现时,使用
interface - 当需要共享一些默认实现时,使用抽象类
小结
在本章中,我们探讨了在面向对象编程中使用 interface 的各种场景:
- 通过
implements定义类的契约 - 通过
extends构建继承体系 - 利用声明合并特性支持插件系统
- 在大型类库和企业级架构中的应用
interface 是 TypeScript 中面向对象编程的重要工具,它帮助我们建立清晰的契约和架构。
下一章,我们将转向函数式编程(FP)的视角,探讨何时更适合使用 type。
思考题: 在你的项目中,是否有通过 interface 构建继承体系或插件系统的经验?遇到了哪些挑战?