Service 层和 Dao 层真的有必要每个类都加上接口吗?

Java之间

共 3198字,需浏览 7分钟

 ·

2021-03-26 12:20

往期热门文章:

1、放弃 Notepad++,事实证明,还有 5 款更牛逼……
2、公司这套架构统一处理 try...catch 这么香,求求你不要再满屏写了,再发现扣绩效!
3、Spring 中经典的 9 种设计模式!收藏了
4、高仿小米商城项目,爱了!
5、为什么有些公司不让用 Lombok ?
来源:toutiao.com/i6882356844245975563
前几天刷头条又刷到了「Service层和Dao层真的有必要每个类都加上接口吗?」这个问题,之前简单回答了一波,给出的观点是「看情况」!
现在结合我参与的项目以及阅读的一些项目源码来看。如果项目中使用了像Spring这样的依赖注入框架,那可以不用接口
先来说说为什么使用了依赖注入框架以后,可以不使用接口!

不需要接口的理由

我整理了支持Service层和Dao层需要加上接口的理由,总结下来就这么三个:
  • 可以在尚未实现具体Service逻辑的情况下编写上层代码,如Controller对Service的调用
  • Spring默认是基于动态代理实现AOP的,动态代理需要接口
  • 可以对Service进行多实现
实际上,这三个理由都站不住脚!
先说说第一个理由:「上层可以在下层逻辑没有实现的情况下进行编码」!很典型的面向接口编程,对层与层之间进行了解耦,看起来好像没有问题。
这种开发方式适合不同模块之间是由不同的人或项目组开发的,因为沟通的成本比较大。同时避免由于项目组之间开发进度的差异而相互影响。
不过让我们回想一下,在一般项目开发里面,有多少项目组是按层来切分开发任务的呢?实际上,大部分的项目都是按照功能划分的。即使是现在前后端分离的情况,单纯的后端开发也是按照功能模块进行任务划分,即一个人负责从Controller层到DAO层的完整逻辑处理。在这种情况下,每一层都先定义一个接口,再去实现逻辑,除了增加了开发人员的工作量(当然,如果代码量计入工作量的话,那开发人员应该也不是太排斥接口的!),实际没有任何用处。
如果开发人员想在下层逻辑没有完成的情况下,先开发上层逻辑,可以先编写下层类的空方法来先完成上层的逻辑。
这里推荐一个个人比较喜欢的开发流程,自上向下的编码流程:
  • 先在Controller层编写逻辑,遇到需要委托Service调用的地方,直接先写出调用代码。优先完成Controller层的流程
  • 然后使用IDE的自动补全,对刚才调用下层的代码生成对应的类和方法,在里面添加TODO
  • 等所有的类和方法都补全了,再基于TODO,按照上面的流程去一个个的完善逻辑。
此方法可以使你对业务流程有比较好的理解。
对于第二个理由,就完全不成立了。Spring默认是基于动态代理的,不过通过配置是可以使用CGLib来实现AOP。CGLib是不需要接口的。
最后一个理由是「可以对Service进行多实现」。这个理由不充分,或者说没有考虑场景。实际上在大多数情况下是不需要多实现,或者说可以使用其它方式替代基于接口的多实现。
另外,对于很多使用了接口的项目,项目结构也是有待商榷的!下面,我们结合项目结构来说明。

项目结构与接口实现

一般项目结构都是按层来划分的,如下所示:
  • Controller
  • Service
  • Dao
对于不需要多实现的情况,也就不需要接口了。上面的项目结构即可满足要求。
对于需要多实现的情况,无论是现在需要,还是后面需要。这种情况下,看起来好像是需要接口。此时的项目结构看起来像这样:
  • Controller
  • Service
  • ----接口在一个包中
  • impl ---实现在另一个包里
  • Dao
对于上面的结构,我们来考虑多实现的情况下,该怎么处理?
第一种方式,是在Service中新增一个包,在里面编写新的逻辑,然后修改配置文件,将新实现作为注入对象。
  • Controller
  • Service
  • ---- 接口在一个包中
  • impl ---实现在另一个包里
  • impl2 ---新实现在另一个包里
  • Dao
第二种方式,是新增一个Service模块,在里面编写新的逻辑(注意这里的包和原来Service的包不能相同,或者包相同,但是类名不同,否则无法创建类。因为在加载时需要同时加载两个Service模块,如果包名和类名都相同,两个模块的类全限定名就是一样的了!),然后修改配置文件,将新逻辑作为注入对象。
  • Controller
  • Service
  • ---- 接口在一个包中
  • impl ---实现在另一个包里
  • Service2
  • impl2 ---新实现在另一个包里
  • Dao
相对而言,实际第一种方式相对更简单一点,只需要关注包层面。而第二种方式需要关注模块和包两个层面。另外,实际这两种方式都导致了项目中包含了不需要的逻辑代码。因为老逻辑都会被打进包里。
不过,从结构上来看,实际方式二的结构要比方式一的结构更清晰,因为从模块上能区分逻辑。
那有没有办法来结合两者的优点呢?答案是肯定的,而且操作起来也不复杂!
首先将接口和实现独立开,作为一个独立的模块:
  • Controller
  • Service --- 接口模块
  • ServiceImpl
  • impl ---实现在另一个包里
  • ServiceImpl2
  • impl2 ---新实现在另一个包里
  • Dao
其次,调整打包配置,ServiceImpl和ServiceImpl2二选一。既然ServiceImpl和ServiceImpl2是二选一,那ServiceImpl和ServiceImpl2的包结构就可以相同。包结构相同了,那调整了依赖以后,依赖注入相关的配置就不需要调整了。调整后,项目结构看起来像这样:
  • Controller
  • Service --- 接口模块
  • ServiceImpl
  • impl ---实现在另一个包
  • ServiceImpl2
  • impl ---新实现和老实现在相同的包中
  • Dao
现在,ServiceImpl和ServiceImpl2模块中的包结构、类名都是一样的。那我们还需要接口模块吗?
假设,我们把Service接口模块去掉,结构变成了如下所示:
  • Controller
  • Service1 --- 老实现
  • Service2 --- 新实现
  • Dao
单纯的通过调整模块依赖,是否能实现Service的多实现?答案显而易见吧?

不使用接口的缺点

上面给出了不使用接口的理由。不过不使用接口并不是完全没有缺点的,主要问题就是在进行多实现的时候,没有一个强接口规范。即不能通过实现接口,借助IDE快速生成框架代码。对于没有实现的接口,IDE也能给出错误提醒。
一个不太优雅的解决是,将原来的模块里的代码拷贝一份到新模块中,基于老代码来实现新的逻辑。
所以,如果一个项目需要多实现、且多实现数量较多(不过一般项目不会有多个实现的),则推荐使用接口。否则不需要使用接口。

总结

本文针对「Service层是否需要接口」这个问题,指出需要接口的理由的问题。以及个人对这个问题的观点,希望对大家有一些帮助。

最近热文阅读:

1、放弃 Notepad++,事实证明,还有 5 款更牛逼……
2、公司这套架构统一处理 try...catch 这么香,求求你不要再满屏写了,再发现扣绩效!
3、Spring 中经典的 9 种设计模式!收藏了
4、高仿小米商城项目,爱了!
5、为什么有些公司不让用 Lombok ?
6、厉害了,为了干掉 HTTP ,Spring 团队开源了 nohttp 项目!
7、别瞎学了,这几门语言要被淘汰了!
8、一个基于 Spring Boot 的项目骨架,非常舒服!
9、Redis 低成本、高可用设计,牛逼!
10、2020年国内互联网公司的薪酬排名!
关注公众号,你想要的Java都在这里

浏览 17
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报