跳到主要内容

基于接口而非实现编程

什么是"基于接口而非实现编程"?

"基于接口而非实现编程"(Program to an interface, not an implementation)是一条非常重要的设计原则,最早出现于1994年GoF的《设计模式》一书中。这个原则的另一种表述是"基于抽象而非实现编程"。

理解"接口"的本质

在这个原则中,"接口"不应局限于编程语言层面的接口语法(如Java中的interface),而是一个更抽象的概念:

  • 从本质上看,"接口"是一组"协议"或"约定"
  • 是功能提供者提供给使用者的一个"功能列表"
  • 在不同场景下有不同的解读(如服务端与客户端之间的接口、类库提供的接口等)

为什么要基于接口编程?

基于接口编程的核心价值在于:

  1. 分离接口和实现:将稳定的接口和不稳定的实现分开
  2. 提高扩展性:上游系统面向接口编程,不依赖具体实现细节
  3. 降低耦合性:当实现发生变化时,上游系统代码基本不需要改动
  4. 应对需求变化:越抽象的设计越能灵活应对未来的需求变化

如何正确使用这个原则?

1. 接口设计的关键点

  • 函数命名不能暴露实现细节
  • 封装具体的实现细节
  • 为实现类定义抽象的接口

2. 实战案例

以图片存储服务为例:

// 定义抽象接口
interface ImageStore {
upload(image: Image, bucketName: string): string;
download(url: string): Image;
}

// 阿里云实现
class AliyunImageStore implements ImageStore {
upload(image: Image, bucketName: string): string {
// 实现上传逻辑
return url;
}

download(url: string): Image {
// 实现下载逻辑
return image;
}

private createBucketIfNotExisting(bucketName: string): void {
// 创建bucket的具体实现
}

private generateAccessToken(): string {
// 生成token的具体实现
}
}

// 私有云实现
class PrivateImageStore implements ImageStore {
upload(image: Image, bucketName: string): string {
// 实现上传逻辑
return url;
}

download(url: string): Image {
// 实现下载逻辑
return image;
}
}

3. 注意事项

  • 接口定义要足够抽象,不要依赖具体实现
  • 不要为每个类都定义接口,要根据实际需求来决定
  • 越是不稳定的系统,越需要在扩展性和维护性上下功夫

什么时候不需要定义接口?

在以下情况下,可以不需要定义接口:

  1. 某个功能只有一种实现方式
  2. 未来也不可能被其他实现方式替换
  3. 系统非常稳定,基本不需要维护
  4. 为了快速开发,暂时不需要扩展性

实践建议

  1. 保持接口的稳定性:接口一旦发布,尽量不要改动
  2. 合理的抽象层次:既不能太抽象导致难以理解,也不能太具体限制了扩展性
  3. 职责单一:接口的设计要符合单一职责原则
  4. 最小化接口:接口中的方法数量要尽可能少,保持精简

总结

基于接口编程是一种非常有效的提高代码质量的手段。它通过分离接口和实现,帮助我们写出更加灵活、可维护的代码。但也要注意不要过度使用,应该根据实际业务场景和系统的稳定性来决定是否需要定义接口。

记住:

  • 接口定义表明做什么,而不是怎么做
  • 在设计接口时要多思考其通用性
  • 替换具体的接口实现时,最好不需要修改接口定义