Skip to content

第 3 章:OOP 之魂 —— 何时用 interface

在前两章中,我们了解了 interfacetype 的本质区别。今天我们来深入探讨在面向对象编程(OOP)中,如何恰当地使用 interface

类的契约:implements

在面向对象编程中,接口最重要的作用之一就是定义类的契约。通过 implements 关键字,我们可以确保类实现了特定的方法和属性:

typescript
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

在复杂的面向对象系统中,接口之间也可以建立继承关系,形成更加清晰的类型层次结构:

typescript
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 等都利用了这一特性:

typescript
// Redux 中间件的例子
interface MiddlewareAPI<S> {
  getState(): S;
  dispatch(action: any): any;
}

interface MiddlewareAPI<S> {
  // 插件可以扩展这个接口
  subscribe(listener: () => void): () => void;
}

这种机制允许第三方库或应用程序的不同模块扩展核心接口,而无需修改原始定义。

适用场景:大型类库、企业级 OOP 架构

在以下场景中,推荐使用 interface

1. 大型类库开发

当你开发一个供他人使用的类库时,使用 interface 可以提供更好的扩展性和兼容性:

typescript
// 类库定义的核心接口
interface DataProcessor<T> {
  process(data: T): Promise<T>;
}

// 用户可以在自己的代码中扩展
interface DataProcessor<T> {
  validate?(data: T): boolean;
}

2. 企业级架构设计

在复杂的企业级应用中,接口可以帮助建立清晰的架构边界:

typescript
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),它们都可以用于定义契约,但有不同的适用场景:

typescript
// 使用 interface
interface Shape {
  getArea(): number;
}

// 使用 abstract class
abstract class AbstractShape {
  abstract getArea(): number;
  
  // 抽象类可以包含具体实现
  toString() {
    return `Area: ${this.getArea()}`;
  }
}

一般来说:

  • 当只需要定义契约而不需要共享实现时,使用 interface
  • 当需要共享一些默认实现时,使用抽象类

小结

在本章中,我们探讨了在面向对象编程中使用 interface 的各种场景:

  1. 通过 implements 定义类的契约
  2. 通过 extends 构建继承体系
  3. 利用声明合并特性支持插件系统
  4. 在大型类库和企业级架构中的应用

interface 是 TypeScript 中面向对象编程的重要工具,它帮助我们建立清晰的契约和架构。

下一章,我们将转向函数式编程(FP)的视角,探讨何时更适合使用 type


思考题: 在你的项目中,是否有通过 interface 构建继承体系或插件系统的经验?遇到了哪些挑战?