大家好我是Louis,有多年一线大厂的架构设计和DDD实战经验。今天来聊一个话题:Java是不是每一层都要定义接口和实现类?
这确实是Java饱受诟病的一个点,也没有谁说一定要这么干,但事实上大家就是这么干了,导致代码看起来特别“啰嗦”,哪怕就1个实现类,也非得加上接口定义。我跟几位资深开发探讨这个话题,他们也说不上来为什么,总之大家都这么干,我不想成为异类
今天我想把这个问题掰扯清楚,每一个这样的小问题背后,都是对架构设计的深度思考。也欢迎大家在评论区交流。
这个问题的开端,还得追溯到最开始的“面向接口编程”。
面向接口编程由来已久,最初是1994年4位编程界大佬(合称GoF)在《Design Patterns》一书中提出Program to an interface, not an implementation.
其中有诸多的模式把接口与实现进行分离,这是大佬们首次向大家展示面向接口编程的价值。
Java语言的贡献。1995年Java首个版本发布就把interface
作为了关键字,这是首次在语言层面对接口这个概念有了直接的支持。结合上面的《Design Patterns》一书,程序员们也很快开始应用设计模式,接口在编程界大放异彩。
Spring的推波助澜。Spring在业界的地位毋庸置疑,特别是在2009年Spring3.0发布后,已经成为了Java后端开发的事实标准。但也是在这一阶段画风慢慢发生了改变,Spring虽没提供规范,但提供了@Controller、@Service等几个注解,程序员逐渐也把代码分为了Controller、Service、Dao这几层,并且每个Service和Dao都会加上接口定义。
更加丧心病狂的是,大约2015年左右,随着微服务的爆火,DDD的设计思想逐渐被接纳,一部分经历过传统三层架构的程序员开始向DDD靠拢,咱知道DDD没别的就是分层多。于是我遇见了前所未有的神奇代码,就是在AppService、DomainService、Factory、Repository、Dao、Converter...所有能想到的都加上了接口!真真正正实现了“面向接口”编程。
由此可见有一大部分人其实是缺少独立思考能力的,只是跟着教条主义,依葫芦画瓢。
接口的作用其实在上面提到的《Design Patterns》中已经有详细说明了,但23种设计模式,并不是每一种都那么常用。
这里我只给大家总结一下在实际项目中更加常见的接口的使用方式:
特别是在跨系统交互上,A系统调用B系统,只需要B系统提供接口即可。这样在定义完接口以后就可以独立开发,还可以对接口进行Mock测试。分别自测完再进行集成。
在系统内,不同模块之间需要用接口做契约吗?我认为视情况而定,但大多数情况下不需要。例如C模块需要调用D模块的能力,完全可以先定义一个空方法,大家想一想,这里Model D一定要用interface吗?
public class ModelC {
private ModelD d;
public void f() {
// ...
d.doSomething(10);
// ...
}
}
publicclass ModelD {
public void doSomething(int a) {
// TODO 暂未实现
}
}
这句话的隐含意思是:实现是可以被替换的。例如定义了一个ID生成器接口
最开始由于业务量小、单机部署,可以直接在内存中生成ID;后来业务量上来了需要改为分布式ID生成器,而且在不同场景下可能会用到基于Redis的实现和基于MySQL的实现。
这时我们就可以实现多个不同的生成器,可以根据不同的业务场景采用不同的实现。但是在调用方无需感知多种不同实现的差异性。
控制反转(IoC)是衡量高阶程序员的一把尺子,我见过许多行云流水的代码,几乎都离不开控制反转的影子。
这其中最典型的例子,就是DDD里面的Repository模式。在传统三层架构中,Service层需要访问数据库,是通过依赖Dao,感知数据的存储方式和实现细节来实现的。然而在DDD中,领域模型才是一等公民,通过建立Repository层实现了控制反转,具体的数据库实现要依赖Repository接口。同样,下图中的缓存访问也是这个道理。
在我的DDD包子铺
项目中也是大量用到控制反转来优化代码结构,后续我会专门拿出一些例子来讲解。
所以,文章开头的那个问题,答案当然是否定的。我们不要一味地追求所谓“代码整齐”,没有实际意义。解决可用性、健壮性、可维护性、兼容性...这些才是关键。
Facade Pattern
,定义Facade接口,再由AppService层去实现。同时Facade接口也作为对外的接口契约Proxy Pattern
,用于隔离上下游的变化关于这些层分别是什么?不了解的同学可以看这篇文章:一张图看懂DDD+CQRS架构
不建议。Domain Service是对内提供的轻量原子服务,其中的逻辑也都是对Entity、Repository、Event的编排,不大可能存在多个不同的实现,没有必要定义接口。
这里其实有一点点小争议,定义接口还有一个好处就是单元测试的时候方便Mock,这个我想专门放到讲Unit Test的时候说。我个人依然不建议定义Domain Service接口。
Factory Pattern
真要讲起来还挺复杂的,又细分为简单工厂、工厂方法、抽象工厂。大家可以自行去搜一搜这几者的差别,其中简单工厂、工厂方法不需要专门定义接口,而抽象工厂就需要定义接口以实现可扩展性。
但我觉得,我们的业务代码中,不适合把工厂设计得过于抽象,一般来说简单工厂就够用了
虽然是小问题,但要琢磨透、讲明白也不容易。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有