向贫血模型宣战
什么是贫血模型
回想一下我们定义的经典代码
1 | public class UserPo { |
这个UserPo
类没有任何行为,只是数据容器,只是为了适应Hibernate
、Mybatis
这些ORM框架而存在。
使用这种模型带来的后果是什么呢?大量的业务逻辑,校验规则都被放到了service层。
举个简单的例子,修改年龄,最常见的做法是:
1 | public class UserService{ |
哪里有不妥帖的地方?我们把所有的业务逻辑放在了UserService
这个类中,比如校验年龄是否符合规则。
什么是充血模型
让我们换一种做法:
1 | public class UserPo { |
看出变化了么:
- 舍弃了常见的getter、setter方法,转而使用更面向业务且更具表现力的命名方式;
- 将参数的校验放到了持久化对象中,使得
UserPo
也拥有了业务逻辑。
贫血模型有什么问题
我们用一个小例子解释了什么是贫血模型,什么是充血模型,但说起来就算用贫血模型,又有什么问题呢?我们有必要舍弃习以为常的模型去追求什么充血模型么?
简单的业务系统采用这种贫血模型和过程化设计是没有问题的,但在业务逻辑复杂了,业务逻辑、状态会散落到在大量方法中,原本的代码意图会渐渐不明确,我们将这种情况称为由贫血症引起的失忆症。
贫血模型跟分层的思想紧密相连,在这种风格中模型只是用来承载数据,业务逻辑由其它类(比如service)来承载。
这种做法带来的一个问题是:我们以为对象就是数据的容器而已,从而失去了面向对象思想的真正关注点。
另外一个实际的问题是,对象的处理会散落在项目各处。还是上面的例子,如果使用充血模型,那么校验年龄就是UserPo
对象的行为,只要使用changeAge()
方法,就会去校验年龄的有效性。我们不必考虑其他service层设置changeAge()
方法时还要校验年龄^1。
重新理解面向对象
回想一下Java编程语言第一课,必然会介绍说:Java是一门面向对象的编程语言,并且有:封装、继承、多态三个特征。
理想状态下我们的对象应该是这个样子的:
1 | public class Dog { |
这时候Dog
是一个更加富含行为,更“面向对象”的对象,我们让它walk()
它就能walking
,让它lieDown()
它马上就lying
了。
但往往下面的代码更为常见的:
1 | public class Dog { |
1 | public class StateService { |
上面的场景里把业务逻辑都放入service层理解起来都没有什么问题,那为什么还要说向贫血模型宣战呢?
因为我们面对的场景从来都不会简单。
面向对象本质上是为了让人更加舒服。以往计算机性能捉襟见肘,程序员们绞尽脑汁想的都是怎么压榨计算机性能,所以本质上那时候程序语言都是服务于计算机的(比如说C,C++)。后来计算机性能上来了有了些富余加上软件的规模越来越大,于是大家开始考虑怎么写出让人更容易理解的代码,这才是面向对象的初心:以人为本。
总结
虽然本文的题目叫做向贫血模型宣战,但我也认为做好抽象设计很难,因此一些编程的方法论还是得读得看。可能有人觉得简单的CRUD项目不需要搞的那么复杂,但好的编程范式尽量遵守,这样在未来面对大型复杂的需求时才能游刃有余。