基于接口而非实现编程,是一条设计原则,它先于很多编程语言诞生,是一条比较抽象、泛化的设计思想。 这条原则中的接口,可以理解为编程语言中的接口或者抽象类。
基于接口而非实现编程的优势
可以提高代码质量,因为应用这条原则,可以将接口和实现相分离,封装不稳定的实现,暴漏稳定的接口。上游系统面向接口而非实现编程,不依赖不稳定的实现细节,这样当实现发生变化的时候,上游系统的代码基本不需要改动,以此来降低耦合性,提高扩展性
如何将这条原则应用到代码中
比如系统中涉及图片处理和存储的业务逻辑,图片经过处理后上传到阿里云。
流程:创建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类中都啄一定义并实现一遍。这样会有以下问题
- ++AliyunImaageStore类中有些函数命名暴漏了实现细节++,比如uploadToaliyun()和downloadFromAliyun()。我们把这种包含aliyun字眼的方法,照抄到PrivateImageStore类中,显然不合适
- ++将图片存储到阿里云的流程,跟存储到私有云的流程,可能并不是完全一致++。比如,阿里云上传中,需要生成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);
}
}
总结
在做软件开发的时候,一定要有抽象意识、封装意识、接口意识。在定义接口的时候,不要暴漏任何实现细节。接口的定义只表明做什么,而不是怎么做。而且在设计接口的时候,要多思考,这样的接口设计是否通用,是否能够做到在替换及具体接口实现的时候,不需要任何接口定义的改动