前几天我在GitHub上看到了一个在线商城的项目,star数上万,我很好奇,这个项目这么多人捧场,应该不错吧!于是把代码下载下来,浏览了一番。
让我惊讶的是,代码写得有点惨,长达几百行的方法比比皆是。没有面向对象编程的思维,用过程性的方式把业务逻辑统统堆砌在Service, 没有领域模型,没有职责的划分。
我不清楚为什么这个项目能获得这么高的star数,如果大家真的觉得这个项目质量好,那就太可怕了。我更倾向于相信很多人觉得这个项目有用,改一改就能当毕业论文,或者当简历中的项目经验吧。
1
这不是我今天想说的重点,回想起我看过的开源项目和经历过的商业项目,有个问题挺突出的:
这些代码中只有Controller, Service ,DAO,并且用过程性的方式把业务逻辑像垃圾一样丢到Service层当中,让Service层变得臃肿不堪,难以维护。
在这些代码中的领域模型被称为“贫血模型”, 特点是只有getter/setter方法,Service层经常做的事情就是:
1. 根据某个ID从数据库获取贫血的对象
2. 创建一个新的贫血对象,执行一堆业务逻辑,调用setter方法装配这些贫血的对象,然后保存到数据库中。
再加上修改和删除,那就是著名的CRUD了。
贫血的领域模型在这里主要承担的是数据容器和数据传输的职责,而不是计算业务逻辑的地方。
关于贫血模型,充血模型, 15年前在javaeye网站上就有过非常充分的讨论(参见文末的链接),没看过的强烈建议读一读。
软件开发大牛Martin Fowler在18年前也出版过一本书,叫做《企业应用架构模式》,其中讲到的“事务脚本”就是这种方法。
没想到这么多年过去了,还是有很多Spring项目在使用事务脚本/贫血模型!
当然,没有东西绝对的,事务脚本/贫血模型也有它适用的地方,那就是对于简单的,没有复杂业务的系统,大部分都是CRUD, 那可以快速地把系统给搞起来。
但是业务逻辑一旦变复杂,Service层就难以承受,变得难以阅读,难以修改,难以维护了。 这时候一定要做出重构,给Service层减负,让业务逻辑呆在它应该呆的地方,即领域模型当中。
2
我在标题中说被Spring带着跑偏了,好像是把锅甩给了Spring,冤枉Spring了,管人家Spring什么事?明明是程序员没有用好嘛!
在网上有一种说法:Spring的作者Rod Johnson也承认,Spring也是在沿袭EJB2时代的“事务脚本”,也就是面向过程编程 !
虽然我没有找到这句话的原始出处,但是不得不说SSH/SSM对很多初学者的误导作用,在SSH/SSM的很多例子当中,用的都是这种贫血模型,这就给初学者带来了一种印象:把贫血的领域对象当作C语言的结构体,把Service当作C语言中的函数来使用,似乎这就是正确的用法。
Martin Fowler提出了贫血模型这一反模式以后,Spring的核心开发人员Craig Walls(也是《Spring实战》的作者)在2005年就写过一篇文章《Spring 2.0 vs 贫血模型》,其中阐述了Spring 2.0如何避免贫血模型,不过他在其中提到的是领域模型自己处理持久化,然后需要引用DAO的问题。
3
很多人给我说工作琐碎,修修补补,很没意思。我建议看一看自己的项目,是不是符合这样的条件:业务复杂+贫血模型,如果是的话就可以学习面向对象的设计,体会一下真正的领域模型。
如果你成功了,看到清爽的、薄薄的Service层,有着充分测试用例的Domain Object,心里会很爽的。
从执行策略上来说,先要想办法获取领导支持,如果没有领导首肯,自己私下里练习一下,然后把漂亮的结果展示给领导看看,更有说服力。
如果你面对这样的代码束手无策的时候,不妨看看这篇文章:《当15年的老司机遇到烂项目、烂代码时,他会做这5件事!》
我知道做出这样的改变是不容易的,但是这么做了才是体现你能力的时候,是让你脱颖而出的时候。
参考文章:
Javaeye的讨论:
https://www.iteye.com/topic/11712
Martin Fowler的文章:
https://www.ituring.com.cn/article/details/25
Craig Walls的文章:
https://martinfowler.com/bliki/AnemicDomainModel.html
【本文为51CTO专栏作者“刘欣”的原创稿件,转载请通过作者微信公众号coderising获取授权】