04 每个类都需要都定义接口吗?
上一节我学习了,接口和抽象类,抽象类和继承的区别以及对于多态的重要性。为什么要多用多组合,少用继承,那么在实际编程中,领导让我实现一个模块的功能,那我怎么样在设计的时候知道该不该用接口呢?
1.工程中实现接口的意义
首先,需要搞清楚接口的工程意义。从本质上来看,“接口”就是一组“协议”或者“约定”,是功能提供者提供给使用者的一个“功能列表”。日常大家都听过的,就是基于接口编程,其实如果不在工程中实际使用,仅仅是面试背诵八股文,那么基于接口编程,仅仅是六字箴言。前一段时间做了一个功能模块,通过实践了解了什么叫基于接口编程。
根据上面的例子,学生只关注去学校,不关注用那种交通方式去,这样即使交通方式改变,也不会影响学生侧的代码逻辑实现。这样就将接口和实现相分离,封装不稳定的实现,暴露稳定的接口。上游系统面向接口而非实现编程,不依赖不稳定的实现细节,这样当实现发生变化的时候,上游系统的代码基本上不需要做改动,以此来降低耦合性,提高扩展性。
在工程中,我们需要考虑的最低级的情况就是实现功能,而高级工程师就是考虑兼容需求的各种变化,而对于未来需求的变化出现的不同的架构和方案,也就是每个工程师能力的体现。
越抽象、越顶层、越脱离具体某一实现的设计,越能提高代码的灵活性,越能应对未来的需求变化。好的代码设计,不仅能应对当下的需求,而且在将来需求发生变化的时候,仍然能够在不破坏原有代码设计的情况下灵活应对。而抽象就是提高代码扩展性、灵活性、可维护性最有效的手段之一
。
2.实际中怎么应用呢?
刚刚开始我们业务是用firebase(类似于发送短信的工具,用于回收数据到业务)打点,用来给产品回收数据。具体实现代码如下:
public class FirebaseReporter {
//... 省略属性、构造函数等...
public void initIfNotExisting() {
// ... 创建 bucket 代码逻辑...
// ... 失败会抛出异常..
}
public String vaildFirebaseScreet() {
// ... 密钥验证
}
public String reportDataToFirebase(String page, String id){
//... 打点到firebase..
}
}
代码功能已经实现了,现在看来没什么问题,初始化,创建链接,打点。但是后续我们产品觉得firebase打点时效性太低了,决定使用实时性打点Pubsub。那么问题来了我们需要完全重新搬写一套,其实这只是打点的一部分,整个项目代码量很大,需要逐一查找修改。如果Friebase和Pubsub都是一个人接入还好,比较熟悉业务,假如是一个新人,那么无疑会带来很多安全隐患,特别是数据有一些时候会影响方向,并且难以发现。
那这两个问题该如何解决呢?
解决这个问题的根本方法就是,在编写代码的时候,要遵从“基于接口而非实现编程”
的原则,具体来讲,我们需要做到下面几点:
-
函数的命名不能暴露任何实现细节。比如,前面提到的 FirebaseReporter() 就不符合要求,应该改为去掉 Firebase 这样的字眼,改为更加抽象的命名方式,比如:Reporter()。
-
封装具体的实现细节。比如vaildFirebaseScreet验证密码不应该使用Public 并且不应该使用这么明确的命名。
-
为实现类定义抽象的接口。具体的实现类都依赖统一的接口定义,遵从一致的上传功能协议。使用者依赖接口,而不是具体的实现类来编程,比如说上报页面,上报点击等等,不应该继续使用reportDataToFirebase()这种无法通用的命名。
总结一下,我们在做软件开发的时候,一定要有抽象意识、封装意识、接口意识。在定义接口的时候,不要暴露任何实现细节。接口的定义只表明做什么,而不是怎么做。而且,在设计接口的时候,我们要多思考一下,这样的接口设计是否足够通用,是否能够做到在替换具体的接口实现的时候,不需要任何接口定义的改动。
这样,新来的小伙便能出色的完成新的数据打点的工作,你就能早点滚蛋了,哈哈哈哈哈。
3.是否需要为每个类定义接口?
1和2讲了意义跟实现,这些网上都可以百度到,个人觉得最重要的还是这一条,仔细理解了这一条,我们就能在实际项目中知道是否该用接口,也就提高了代码的抽象程度和可扩展程度。
做任何事情都要讲求一个“度”,过度使用这条原则,非得给每个类都定义接口,接口满天飞,也会导致不必要的开发负担。什么时候不需要定义接口,直接使用实现类编程,我们做权衡的根本依据,还是要回归 到设计原则诞生的初衷上来。或者你仔细看了我再2中的例子,你或许也能理解接口的使用场景。
这条原则的设计初衷是,将接口和实现相分离,封装不稳定的实现,暴露稳定的接口
。上游系统面向接口而非实现编程,不依赖不稳定的实现细节,这样当实现发生变化的时候,上游系统的代码基本上不需要做改动,以此来降低代码间的耦合性,提高代码的扩展性。
这个十分重要,在编写代码的过程中,这一点既考验你的编程素养,也考验你对于项目的熟悉程度,到底这个模块是稳定还是要经常变动的? 如果在我们的业务场景中,某个功能只有一种实现方式,未来也不可能被其他实现方式替换,那我们就没有必要为其设计接口,也没有必要基于接口编程,直接使用实现类就可以了。
写完这一篇文章后,我又重温了一遍,什么时候改用接口,接口的好处,以及怎么抽象接口的思想。
转载自:https://juejin.cn/post/7306459858126635058