设计原则-基于接口而非实现编程

目录

1.实战应用

2.是否需要为每个类都定义接口?


基于接口而非实现编程是一种非常有效地提高代码质量的手段,在平时的开发中会经常被用到。这里面的接口可以理解为编程语言中的接口或者抽象类。应用这条原则可以将接口和实现相分离,封装不稳定的实现,暴露稳定的接口。上游系统面向接口而非实现编程,不依赖不稳定的实现细节,这样当实现发生变化的时候,上游系统的代码基本上不需要做改动,以此来降低耦合性,提高扩展性。

1.实战应用

假设我们的系统中有很多涉及图片处理和存储的业务逻辑,图片经过处理之后被上传到阿里云上。为此我们统一提供了AliyunImageStore类,供整个系统使用:


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);
  }
  
}

整个上传流程包含三个步骤:创建bucket、生成access token访问凭证、携带access token上传图片到指定的bucket中。代码实现非常简单,类中的几个方法定义都很干净,用起来也很清晰,完全可以满足我们存储图片到阿里云的需求。不过,软件开发中唯一不变的就是变化。过了一段时间,如果我们自建了私有云,不再将图片存储到阿里云,而是存储到自建私有云上。为了满足这个变化,我们该如何修改代码?

首先,AliyunImageStore类中有些函数命名暴露了实现细节,比如uploadToAliyun()和downloadFromAliyun()。此时,我们需要把这种包含“aliyun”字眼的类和方法全部重新命名,代码的改动量可能会很大。其次,将图片存储到阿里云的流程跟存储到私有云的流程,可能并不完全一致的。比如,私有云可能并不需要access token。那这两个问题应该如何解决呢?解决这个问题的根本方法就是,在编写代码的时候,要遵从“基于接口而非实现编程”的原则,具体来讲,需要做到以下三点:

  • 方法的命名不能暴露任何实现细节,比如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);
  }
}

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

2.是否需要为每个类都定义接口?

为了满足这条原则,我们是不是需要给每个实现类都定义对应的接口呢?其实,做任何事情都要讲究一个度,过度使用这条原则,会导致每个类都定义了接口,接口满天飞,也会导致不必要的开发负担。

从该原则的设计初衷上来看,如果我们的业务场景中,某个功能只有一种实现方式,未来也不可能被其他实现方式替换,那我们就没有必要为其设计接口,也没必要基于接口编程,直接使用实现类就可以了。除此之外,越是不稳定的系统,我们越要在代码的扩展性、维护性上下功夫。相反,如果某个系统特别稳定,在开发完之后,基本上不需要做维护,那么我们就没有必要为其扩展性,投入不必要的开发时间。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值