提出问题
我觉得做过 Java Web 项目的程序员,代码数量敲到一定程度的时候,都会产生这样的疑惑:MVC 层中的 Service 层,Dao 层的接口到底有什么用?是否是为了使用接口而使用接口?
不用接口并不代表就不能解耦,就不是 OOP 。
尤其是在事实上,99% 的场景都是接口与实现类严格一一对应的。在这种情况下,接口究竟有什么意义?
是为了使用 JDK 基于接口的动态代理吗?但基于 CGLib 的动态继承也同样可以实现 AOP,并且 Spring 本身也对两种技术都提供了支持,所以技术上的理由也同样是不充分的。
使用接口的好处
我们来看看我们通常可以想得到的,使用接口的好处:
为每个 Dao 层声明接口的好处在于:
- 可以在尚未实现具体 Dao 方法时就编写上层代码,如在 Service 层中对 Dao 的调用;
- 可以为 Dao 提供多个实现, 例如有 JDBCDao 实现,MyBatisDao 实现,而不需要修改上层代码,只需要简单的在 Spring 中控制 Dao 对象的注入即可。
Service 接口:
- 可以在尚未实现具体 Service 情况下编写上层代码,如 Controller 层对 Service 的调用;
- 可以对 Service 提供多实现。
另外,使用接口对于测试代码也是有好处的。测试者不需要关注方法的具体实现,可以基于接口直接提供 Mock 方法。所以使用接口对于快速实现流程上的测试是有好处的。
上面说了一些使用接口的有点,但是好像都并不是很明显。只要我们的实现与接口一一对应,实现类也完全可以完成这些职责。
之前也说了,不用接口并不代表就不能解耦,但使用接口确实可以更好地实现解耦。尤其体现在:
- 接口与实现是不同模块,或不同团队提供(虽然并太常见);
- 开发框架。
框架开发应该是比较常见的必须使用接口的案例了,比如开发 Spring,Struts 。这时你没有任何实现代码,也必须面向接口开发。
使用接口的坏处
相较于模棱两可的优势,使用接口的坏处可以说非常明显了:
- 重复代码,增大了编写,维护,调试成本;
- 增大系统复杂度,对系统的理解变得困难。
绕过接口的最大坏处是不可控的类信息暴露控制。
引述知乎上一个真实的案例(链接见文末):
比如我们打算用 AOP 来实现 service 方法级别的缓存,并用 annotation 来标记需要缓存的方法和缓存控制声明。
如果使用接口层,实现就很简单了,只需要在接口的方法声明上标记 annotation 就可以了。
如果没有接口呢?显然,我们只能把 annotation 直接标记到类方法上。看起来没啥区别是吗?但是,直接在类方法上标记可能会导致一个意外的错误,就是开发者有时会将 annotation 标记到 private 方法上去。
很多同学这时候应该已经会心一笑了,基于 CGLib 的类继承方式实现的 AOP 是不能在 private 也不能在 final 方法上生效的。所以,开发者可能会自以为已经缓存了一个很复杂的方法实现,但实际上却完全没有生效。
反过来,这样的错误,在基于接口的方式下,是几乎不可能出现的。开发者如果发现需要缓存一个很重的 private 方法,要么会很省事的把这个方法改为 public 并添加到接口,或者更有责任心的考虑一下,是否需要修改接口设计让整个 Service 的体系结构更加合理。
「上面说过的错误,我们实际上是的的确确出现过的,但这个错误,是在我们一开始决定不使用接口的情况下就非常清楚这个风险的,我们在事前经过讨论,认为这个风险是可以接受的,同样在工程实践中,即使真正出现了这样的错误,我们讨论评估之后的结论仍然是,我们可以接受偶尔出现这样的错误,所以我们决定继续这样的设计风格。」
作者选择不使用接口层的主要理由有:
- 我们是小团队,大家的技术水平相对较高且平均,出现这样的错误情况并不多见,从概率上可以接受。
- 同样,因为是小团队,所以虽然没有刻意的做代码 review,但是在编码的同时代码的交叉 review 是相当频繁的,所以,即使出现错误,发现的概率也是很高的。
- 我们不是面向企业的业务系统开发,我们是做公开的 service site,是面向互联网的服务,我们的业务逻辑变更非常频繁,所以,没有接口的情况下,我们可以少写代码,也更易于变更,少了一个接口抽象层,代码阅读体验也大大提高,这点让我们觉得更加重要。
反之,大多数 Java Web 项目开可能都是面向企业应用的开发,开发人员的技术水平可能参差不齐,或者是非常大型的开发团队。这两种情况下,意外错误的风险都会被放大,而通过接口来约束并保证不出现意外就显得更加重要了。
It depends
项目中是否一定需要接口层,总的来说还是需要具体问题具体分析。
这取决于项目团队的技术能力,团队规模,以及对开发成本和开发质量的平衡考量。
大部分情况下,还是推荐遵循面向接口的开发流程,通过约束减小错误对系统的影响。
参考文章:
文章评论