越是不稳定的系统,我们越是要在代码的扩展性、维护性上下功夫。
文章目录
09 | 理论六:为什么基于接口而非实现编程?有必要为每个类都定义接口吗?
1. 如何解读原则中的“接口”二字?
- 越抽象、越顶层、越脱离具体某一实现的设计,越能提高代码的灵活性,越能应对未来的需求变化。好的代码设计,不仅能应对当下的需求,而且在将来需求发生变化的时候,仍然能够在不破坏原有代码设计的情况下灵活应对。
2. 如何将这条原则应用到实战中?
- 在编写代码的时候,要遵从“基于接口而非实现编程”的原则(只表明做什么而不是怎么做):
- 函数的命名不能暴露任何实现细节。比如,前面提到的 uploadToAliyun() 就不符合要求,应该改为去掉 aliyun 这样的字眼,改为更加抽象的命名方式,比如:upload()。
- 封装具体的实现细节。比如,跟阿里云相关的特殊上传(或下载)流程不应该暴露给调用者。我们对上传(或下载)流程进行封装,对外提供一个包裹所有上传(或下载)细节的方法,给调用者使用。
- 为实现类定义抽象的接口。具体的实现类都依赖统一的接口定义,遵从一致的上传功能协议。使用者依赖接口,而不是具体的实现类来编程。
3. 是否需要为每个类定义接口?
- 并不是。设计初衷是将接口和实现相分离,封装不稳定的实现,暴露稳定的接口。以此降低耦合,提高扩展性。
- 因此,如果在我们的业务场景中,某个功能只有一种实现方式,未来也不
可能被其他实现方式替换,那我们就没有必要为其设计接口,也没有必要基于接口编程,直接使用实现类就可以了。
4. 课堂讨论
- 在今天举的代码例子中,尽管我们通过接口来隔离了两个具体的实现。但是,在项目中很多地方,我们都是通过下面第 8 行的方式来使用接口的。这就会产生一个问题,那就是,如果我们要替换图片存储方式,还是需要修改很多类似第 8 行那样的代码。这样的设计还是不够完美,对此,你有更好的实现思路吗?
// ImageStore 的使用举例 public class ImageProcessingJob { private static final String BUCKET_NAME = "ai_images_bucket"; //... 省略其他无关代码... public void process() { Image image = ...;// 处理图片,并封装为 Image 对象 8 ImageStore imageStore = new PrivateImageStore(/* 省略构造函数 */); imagestore.upload(image, BUCKET_NAME); } }
- 两种方法:
- 简单工厂+反射+配置
- 通过使用控制反转/依赖注入方式,类似于Spring