04 每个类都需要都定义接口吗?

上一节我学习了,接口和抽象类,抽象类和继承的区别以及对于多态的重要性。为什么要多用多组合,少用继承,那么在实际编程中,领导让我实现一个模块的功能,那我怎么样在设计的时候知道该不该用接口呢?

1.工程中实现接口的意义

首先,需要搞清楚接口的工程意义。从本质上来看,"接口"就是一组"协议"或者"约定",是功能提供者提供给使用者的一个"功能列表"。日常大家都听过的,就是基于接口编程,其实如果不在工程中实际使用,仅仅是面试背诵八股文,那么基于接口编程,仅仅是六字箴言。前一段时间做了一个功能模块,通过实践了解了什么叫基于接口编程。

根据上面的例子,学生只关注去学校,不关注用那种交通方式去,这样即使交通方式改变,也不会影响学生侧的代码逻辑实现。这样就将接口和实现相分离,封装不稳定的实现,暴露稳定的接口。上游系统面向接口而非实现编程,不依赖不稳定的实现细节,这样当实现发生变化的时候,上游系统的代码基本上不需要做改动,以此来降低耦合性,提高扩展性。

在工程中,我们需要考虑的最低级的情况就是实现功能,而高级工程师就是考虑兼容需求的各种变化,而对于未来需求的变化出现的不同的架构和方案,也就是每个工程师能力的体现。

越抽象、越顶层、越脱离具体某一实现的设计,越能提高代码的灵活性,越能应对未来的需求变化。好的代码设计,不仅能应对当下的需求,而且在将来需求发生变化的时候,仍然能够在不破坏原有代码设计的情况下灵活应对。而抽象就是提高代码扩展性、灵活性、可维护性最有效的手段之一

2.实际中怎么应用呢?

刚刚开始我们业务是用firebase(类似于发送短信的工具,用于回收数据到业务)打点,用来给产品回收数据。具体实现代码如下:

js 复制代码
public class FirebaseReporter {
 //... 省略属性、构造函数等...
 
 public void initIfNotExisting() {
 // ... 创建 bucket 代码逻辑...
 // ... 失败会抛出异常..
 }
 
 public String vaildFirebaseScreet() {
 // ... 密钥验证
 }
 
 public String reportDataToFirebase(String page, String id){
 //... 打点到firebase..
 }
 
}

代码功能已经实现了,现在看来没什么问题,初始化,创建链接,打点。但是后续我们产品觉得firebase打点时效性太低了,决定使用实时性打点Pubsub。那么问题来了我们需要完全重新搬写一套,其实这只是打点的一部分,整个项目代码量很大,需要逐一查找修改。如果Friebase和Pubsub都是一个人接入还好,比较熟悉业务,假如是一个新人,那么无疑会带来很多安全隐患,特别是数据有一些时候会影响方向,并且难以发现。

那这两个问题该如何解决呢?

解决这个问题的根本方法就是,在编写代码的时候,要遵从"基于接口而非实现编程"的原则,具体来讲,我们需要做到下面几点:

  1. 函数的命名不能暴露任何实现细节。比如,前面提到的 FirebaseReporter() 就不符合要求,应该改为去掉 Firebase 这样的字眼,改为更加抽象的命名方式,比如:Reporter()。

  2. 封装具体的实现细节。比如vaildFirebaseScreet验证密码不应该使用Public 并且不应该使用这么明确的命名。

  3. 为实现类定义抽象的接口。具体的实现类都依赖统一的接口定义,遵从一致的上传功能协议。使用者依赖接口,而不是具体的实现类来编程,比如说上报页面,上报点击等等,不应该继续使用reportDataToFirebase()这种无法通用的命名。

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

这样,新来的小伙便能出色的完成新的数据打点的工作,你就能早点滚蛋了,哈哈哈哈哈。

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

1和2讲了意义跟实现,这些网上都可以百度到,个人觉得最重要的还是这一条,仔细理解了这一条,我们就能在实际项目中知道是否该用接口,也就提高了代码的抽象程度和可扩展程度。

做任何事情都要讲求一个"度",过度使用这条原则,非得给每个类都定义接口,接口满天飞,也会导致不必要的开发负担。什么时候不需要定义接口,直接使用实现类编程,我们做权衡的根本依据,还是要回归 到设计原则诞生的初衷上来。或者你仔细看了我再2中的例子,你或许也能理解接口的使用场景。

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

这个十分重要,在编写代码的过程中,这一点既考验你的编程素养,也考验你对于项目的熟悉程度,到底这个模块是稳定还是要经常变动的? 如果在我们的业务场景中,某个功能只有一种实现方式,未来也不可能被其他实现方式替换,那我们就没有必要为其设计接口,也没有必要基于接口编程,直接使用实现类就可以了。

写完这一篇文章后,我又重温了一遍,什么时候改用接口,接口的好处,以及怎么抽象接口的思想。

相关推荐
玛丽莲茼蒿5 小时前
javaSE 集合框架(五)——java 8新品Stream类
java·开发语言
程序员小假5 小时前
设计一个支持万人同时抢购商品的秒杀系统?
java·后端
L***d6705 小时前
Spring Boot(七):Swagger 接口文档
java·spring boot·后端
C雨后彩虹5 小时前
竖直四子棋
java·数据结构·算法·华为·面试
疾风sxp5 小时前
nl2sql技术实现自动sql生成之langchain4j SqlDatabaseContentRetriever
java·人工智能·langchain4j
一勺菠萝丶6 小时前
PDF24 转图片出现“中间横线”的根本原因与终极解决方案(DPI 原理详解)
java
姓蔡小朋友6 小时前
Unsafe类
java
一只专注api接口开发的技术猿6 小时前
如何处理淘宝 API 的请求限流与数据缓存策略
java·大数据·开发语言·数据库·spring
荒诞硬汉6 小时前
对象数组.
java·数据结构
期待のcode6 小时前
Java虚拟机的非堆内存
java·开发语言·jvm