服装公司网站建设策划书,低价做网站,dedecms 做的医院网站,百度如何投放广告前言
“基于接口而非实现编程”这个原则非常重要#xff0c;是一种非常有效的提高代码质量的手段#xff0c;在平时的开发中经常被用到。 如何解读原则中的“接口”二字
要理解“基于接口而非实现编程”的关键就是要理解其中的“接口”二字#xff0c;我们可以理解为编程语…前言
“基于接口而非实现编程”这个原则非常重要是一种非常有效的提高代码质量的手段在平时的开发中经常被用到。 如何解读原则中的“接口”二字
要理解“基于接口而非实现编程”的关键就是要理解其中的“接口”二字我们可以理解为编程语言中的接口或抽象类。 前面我们提到这条原则非常有效地提高代码质量因为这条原则可以将接口与实现分离封装不稳定的实现暴露稳定的接口。上游系统面向接口而非实现编程不依赖不稳定的实现这样当实现放生变化的时候上游系统的代码基本上不需要做改动以此来降低耦合提高扩展性。 “基于接口而非实现编程”这条原则的另一个表达方式是“基于抽象而非实现编程”。在软件开发中最大的挑战之一就是需求不断变化这也是考验代码设计好坏的一个标准。越抽象、越顶层、越脱离具体某一实现的设计越能提高代码的灵活性越能应对未来的需求变化。好的代码设计不仅能应对当下的需求而且在将来需求发生变化的时候仍然能够在不破坏原有代码设计去情况下灵活应对。而抽象就是提高代码扩展性、灵活性、可维护性最有效的手段之一。
如何将这条原则应用到实战中
假设系统中有很多设计图片处理和存储的业务逻辑。图片经过处理之后被上传到阿里云上。为了代码复用我们封装了图片存储相关的代码逻辑提供了一个统一的 AliyunImageStore 类。
public class AliyunImageStore {// 省略构造属性、构造函数...public void createBucketIfNotExists(String bucketName) {// 创建bucket代码逻辑...// 失败或抛出异常}public String generateAccessKey() {// 根据accessKey/secretKey等生成 access Token...}public String uploadToAliyun(Image image, String bucketName, String accessToken) {// 上传图片到阿里云...// 返回图片存储在阿里云上的地址(url...}public String downloadFromAliyun(String url, String accessToken) {// 从阿里云下载图片...}
}// AliyunImageStore类的使用举例
public class ImageProcessJob {private static final String BUCKET_NAME ai_image_bucket;// 省略其他无关代码...public void process() {Image image ...; //处理图片并封装为Image对象AliyunImageStore imageStore new AliyunImageStore(/*省略参数*/);imageStore.createBucketIfNotExists(BUCKET_NAME);String accessToken imageStore.generateAccessKey();imageStore.uploadToAliyun(image, BUCKET_NAME, accessToken);}
}整个上传流程分为三步创建 bucket简单理解为目录、生成 accessToken、携带 accessToken 上传图片到指定的 bucket 中。咋看起来这段代码没有太大问题完全能满足我们将图片存储在阿里云的需求。
软件开发经常会发生需求变化。假设我们自建了私有云不在将图片存储到阿里云了而是将图片存储到自建云上。此时该如何修改代码呢
需要重新设计一个 PrivateImageStore 类并用它来替掉项目中所有的 AliyunImageStore 类。这样的修改看起来并不复杂知识简单替换而已对整个代码的改动不大。不过我们常说“细节是魔鬼”。实际上刚刚的设计实现方式就因此了很多容易出问题的“魔鬼细节”。
新的 PrivateImageStore 类需要设计哪些方式才能在尽量最小化代码修改的情况下替换掉 AliyunImageStore 呢这就要求我们必须将 AliyunImageStore 类中定义的所有 public 方法都注意定义并实现以便。但是这样做忽悠一些问题
首先 AliyunImageStore 类中有些函数命名暴露了细节比如 uploadToAliyun() 和 downloadFromAliyun()。如果开发这个功能的人没有接口意识、抽象思维那这种暴露细节的命名方式就不足为奇了。而我们把包含 aliyun 字眼的方法照抄到 PrivateImageStore 类中显然是不合适的。如果我们在新类中重新命名 uploadToAliyun() 和 downloadFromAliyun() 这些方法那就意味着我们要修改项目中所有使用到这两个方法的代码代码修改量可能会很大。其次将图片存储到阿里云的流程和存储到私有云的流程可能并不完全一致。比如阿里云的图片上传和下载的过程中都需要生产 accessToken而私有云不需要。一方面AliyunImageStore 中定义的 generateAccessKey() 方法不能照抄到 PrivateImageStore 类中另一方面我们在使用 AliyunImageStore 上传、下载图片的时候代码中个用到了 generateAccessKey() 方法如果要改为私有云的上传下载流程这些代码都需要调整。
如何解决上述两个问题呢那就是要遵从“基于接口而非实现编程”的原则具体来讲需要做到 3 点
函数的命名不能暴露任何实现细节。比如前面提到的 uploadToAliyun() 就不符合要求要去掉 aliyun 这样的字眼改为更加抽象的命名方式比如 upload() 。封装具体实现。比如跟阿里云相关的特殊上传或下载流程不应该暴露给调用者。我们对上传或下载的流程进行封装对外提供一个包裹所有上传或下载的细节的方法给调用者使用。为实现类定义接口。具体的实现类都依赖统一的接口定义遵从一致的上传功能协议。使用者依赖接口而不是依赖实现类来编程。
按照这个思路重构以下上面的代码。
public interface ImageStore {String upload(Image image, String bucketName);Image download(String bucketName);
}public class AliyunImageStore implements ImageStore {// 省略属性、构造函数等...Overridepublic String upload(Image image, String bucketName) {createBucketIfNotExists(bucketName);String accessToken generateAccessKey();// 上传图片到阿里云...// 返回图片存储在阿里云上的地址(url...}Overridepublic Image download(String bucketName) {String accessToken generateAccessKey();// 从阿里云下载图片...}public void createBucketIfNotExists(String bucketName) {// 创建bucket代码逻辑...// 失败或抛出异常}public String generateAccessKey() {// 根据accessKey/secretKey等生成 access Token...}
}// 上传流程改变私有云不需要accessToken
public class PrivateImageStore implements ImageStore {// 省略属性、构造函数等...Overridepublic String upload(Image image, String bucketName) {createBucketIfNotExists(bucketName);// 上传图片到私有云...// 返回图片存储在私有云上的地址(url...}Overridepublic Image download(String bucketName) {// 从私有云下载图片...}public void createBucketIfNotExists(String bucketName) {// 创建bucket...// 失败或抛出异常}
}// ImageStore类的使用举例
public class ImageProcessJob {private static final String BUCKET_NAME ai_image_bucket;// 省略其他无关代码...public void process() {Image image ...; //处理图片并封装为Image对象ImageStore imageStore new PrivateImageStore(/*省略参数*/);imageStore.upload(image, BUCKET_NAME);}
}有很多人在定义接口的时候希望通过实现类来反推接口的定义。如果按照这种的方式可能导致接口定义不够抽象依赖具体的实现。这样的接口设计就没有意义了。 如果你觉得这种思考方式更加顺畅那也没问题知识将实现类的方法搬迁到接口定义中的时候要有选择性的搬移不要将跟具体实现相关的方法搬移到接口中比如 AliyunImageStore 中的 generateAccessKey() 方法。 我们在做软件开发的时候一定要有抽象意识、封装意识、接口意识。在定义接口的时候不要暴露任何细节。接口的定义只表明要做什么而不是怎么做。而且在设计接口的时候要多思考这样的接口设计是否足够通用是否能够做到在替换具体的接口实现时不需要任何接口定义的改动。
是否需要为每个类定义接口
做任何事情需要讲究一个“度”过度使用这条原则非得给每个类都定义接口接口满天飞也会导致不必要的负担。
至于什么时候该为某个类设计接口实现基于接口的编程什么时候不需要定义接口直接使用实现类编程我们要做权衡的根本依据还是要回归到设计原则诞生的初衷上来。只要搞清楚了这条规则很多之前模棱两可的问题都会变得豁然开朗。
前面已经讲过这条原则的设计初衷是将接口和实现分离封装不稳定的实现暴露稳定的接口。上游系统面向接口而非实现编程不依赖不稳定的实现细节这样当实际发生变化的时候上游系统的代码基本上不需要做改动依次来降低代码间的耦合性提高代码的扩展性。
从这个涉及初衷来看如果我们的业务场景中某个功能只有一个实现方式未来也不可能被其他实现方式替换那我们就没有必要为其设计接口也没有必要基于接口编程直接使用实现类编程就可以了。
初次之外越是不稳定的系统我们越是要在代码的扩展性、维护性上下功夫。相反如果某个系统特别稳定在开发完成之后基本上不需要做维护那我们就没有必要为其扩展性投入不必要的开发时间。
总结
基于接口而非实现编程这条原则的另一个表达方式是“基于抽象而非实现编程”我们在开发的时候一定要有抽象意识、封装意识、接口意识。越抽象、越顶层、越脱离具体某一实现的设计越能提高代码的灵活性、扩展性和可维护性。在定义接口的时候一方面命名要足够通用不能包含跟具体实现相关的字眼另一方面与特定实现有关的方法不要定义在接口中。“基于接口而非实现编程”这条原则不仅仅可以指导非常细节的编程开发还能指导更加上层的架构设计、系统设计等。比如客户端与服务器端之间的“接口设计”、类库的“接口”设计。