设计模式、面向对象五:基于接口而非实现编程

基于接口而非实现编程,是一条设计原则,它先于很多编程语言诞生,是一条比较抽象、泛化的设计思想。 这条原则中的接口,可以理解为编程语言中的接口或者抽象类。

基于接口而非实现编程的优势

可以提高代码质量,因为应用这条原则,可以将接口和实现相分离,封装不稳定的实现,暴漏稳定的接口。上游系统面向接口而非实现编程,不依赖不稳定的实现细节,这样当实现发生变化的时候,上游系统的代码基本不需要改动,以此来降低耦合性,提高扩展性

如何将这条原则应用到代码中

比如系统中涉及图片处理和存储的业务逻辑,图片经过处理后上传到阿里云。
流程:创建bucket(存储目录)、生成access token 访问凭证、携带access token上传图片到指定的bucket中


public class AliyunImageStore {
  //... 省略属性、构造函数等...
  
  public void createBucketIfNotExisting(String bucketName) {
    // ... 创建 bucket 代码逻辑...
    // ... 失败会抛出异常..
  }
  
  public String generateAccessToken() {
    // ... 根据 accesskey/secrectkey 等生成 access token
  }
  
  public String uploadToAliyun(Image image, String bucketName, String accessToken) {
    //... 上传图片到阿里云...
    //... 返回图片存储在阿里云上的地址 (url)...
  }
  
  public Image downloadFromAliyun(String url, String accessToken) {
    //... 从阿里云下载图片...
  }
}

// AliyunImageStore 类的使用举例
public class ImageProcessingJob {
  private static final String BUCKET_NAME = "ai_images_bucket";
  //... 省略其他无关代码...
  
  public void process() {
    Image image = ...; // 处理图片,并封装为 Image 对象
    AliyunImageStore imageStore = new AliyunImageStore(/* 省略参数 */);
    imageStore.createBucketIfNotExisting(BUCKET_NAME);
    String accessToken = imageStore.generateAccessToken();
    imagestore.uploadToAliyun(image, BUCKET_NAME, accessToken);
  }
  
}

这段代码很干净,满足我们的需求。
但是软件开发中唯一不变的就是变化。过了一段时间,我们建立了私有云,不再将图片存到阿里云,而是将图片存储到私有云,为了满足这个需求,我们设计新的privaateImaageStore类,替换项目中所有的aliyunImageStore类。
如何修改
我们必须将AliyunImaageStore类中所有的public方法,在PrivateImageStore类中都啄一定义并实现一遍。这样会有以下问题

  1. ++AliyunImaageStore类中有些函数命名暴漏了实现细节++,比如uploadToaliyun()和downloadFromAliyun()。我们把这种包含aliyun字眼的方法,照抄到PrivateImageStore类中,显然不合适
  2. ++将图片存储到阿里云的流程,跟存储到私有云的流程,可能并不是完全一致++。比如,阿里云上传中,需要生成accesstoken,而私有云不需要

如何解决
解决这个问题的根本就是在编写代码的时候,要遵从‘基于接口而非实现编程’的原则,具体,我们需要做到下面3点

  • 函数的命名不能暴漏任何细节。比如前面的uploadToAliyun(),应该取到aliyun字眼,改成更加抽象的命名方式,比如upload
  • 封装具体的实现细节。比如跟阿里云相关的特殊上传(或下载)流程不应该暴漏给调用者,应该把上传(或下载)流程进行封装,对外提供一个包裹所有上传(或下载)细节的方法,给调用者
  • 为实现类定义抽象的接口。 具体的实现类都依赖统一的接口定义,遵从一致的上传功能协议。使用者依赖接口,而不是具体的实现类来编程

重构后的代码如下


public interface ImageStore {
  String upload(Image image, String bucketName);
  Image download(String url);
}

public class AliyunImageStore implements ImageStore {
  //... 省略属性、构造函数等...

  public String upload(Image image, String bucketName) {
    createBucketIfNotExisting(bucketName);
    String accessToken = generateAccessToken();
    //... 上传图片到阿里云...
    //... 返回图片在阿里云上的地址 (url)...
  }

  public Image download(String url) {
    String accessToken = generateAccessToken();
    //... 从阿里云下载图片...
  }

  private void createBucketIfNotExisting(String bucketName) {
    // ... 创建 bucket...
    // ... 失败会抛出异常..
  }

  private String generateAccessToken() {
    // ... 根据 accesskey/secrectkey 等生成 access token
  }
}

// 上传下载流程改变:私有云不需要支持 access token
public class PrivateImageStore implements ImageStore  {
  public String upload(Image image, String bucketName) {
    createBucketIfNotExisting(bucketName);
    //... 上传图片到私有云...
    //... 返回图片的 url...
  }

  public Image download(String url) {
    //... 从私有云下载图片...
  }

  private void createBucketIfNotExisting(String bucketName) {
    // ... 创建 bucket...
    // ... 失败会抛出异常..
  }
}

// ImageStore 的使用举例
public class ImageProcessingJob {
  private static final String BUCKET_NAME = "ai_images_bucket";
  //... 省略其他无关代码...
  
  public void process() {
    Image image = ...;// 处理图片,并封装为 Image 对象
    ImageStore imageStore = new PrivateImageStore(...);
    imagestore.upload(image, BUCKET_NAME);
  }
}

总结
在做软件开发的时候,一定要有抽象意识、封装意识、接口意识。在定义接口的时候,不要暴漏任何实现细节。接口的定义只表明做什么,而不是怎么做。而且在设计接口的时候,要多思考,这样的接口设计是否通用,是否能够做到在替换及具体接口实现的时候,不需要任何接口定义的改动

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值