论坛首页 Java企业应用论坛

是否应该让实体类具备丰富的业务逻辑?

浏览 67207 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (1)
作者 正文
   发表时间:2005-03-22  
围绕领域模型,围绕贫血的domain object,围绕七七八八的讨论已经很多的,摒弃那些无聊的争论,让我们就问题的真正本质展开分析和讨论。

问题的本质就是是否应该让实体类具备丰富的业务逻辑语义


一种观点认为,实体类应该仅仅保持对象状态,只承担映射数据库表的职责。而丰富的业务逻辑语义应该被剥离出去。实体类和DAO层都应该是持久层的职责,而业务逻辑语义则应该是业务对象的完成,即业务层的职责。

从这一点来说,过去的软件模型,包括EJB模型和Hibernate模型,都是属于此类的,他们之间的不同在于实体类和DAO是否绑定,他们的共同点在于实体类和业务逻辑都是分离开来的。

基本上我是持这一方的观点的。这种模型的优点在于分离了关注点,不同层完成不同的职责,特别是基于轻量级框架下的模型,由很多细颗粒度的POJO组成,各自承担不同的职责,提高了软件系统的灵活性和可维护性。它的缺点在于比较多的细颗粒度业务对象,模块内聚性不高。目前主流软件系统架构都是这种模型。

另一种观点认为,实体类除了保持对象状态之外,还应该封装和该对象相关联的丰富业务逻辑语义,即等于把实体类和业务逻辑对象合而为一。特别是Martin Fowler的DDD一出,很多人立刻转向该种观点。

这种软件模型的优点在于提高了对象状态和业务逻辑的内聚性,它的缺点在于往一个类里面放入了过多的职责,不符合单一职责的原则。目前尚没有主流软件架构使用该模型。

我个人是持第一种模型的观点,但是也很希望听取一下持第二种观点人的意见,了解一下该模型到底有什么实际的好处? 我想,这里搬出什么理论大道理,搬出什么Martin Fowler的话都不是我想听的,我想了解的是第二种模型究竟在实际软件开发中,能够给我们带来什么好处?
   发表时间:2005-03-22  
robbin 写道
这种软件模型的优点在于提高了对象状态和业务逻辑的内聚性,它的缺点在于往一个类里面放入了过多的职责,不符合单一职责的原则。


首先反对这个说法,实体类在业务层才会拥有业务逻辑,并且拥有的业务逻辑也应该是单一的,不会随便加入任何逻辑在一个类里边。
0 请登录后投票
   发表时间:2005-03-22  
找来一点背景讨论:

gigix 写道
我们现在的做法是Transaction Script模式(http://www.martinfowler.com/eaaCatalog/transactionScript.html)。实体(也就是需要持久化的对象,有时我们也说它们就是领域对象)只是原始数据的对象形式,或者加上一些最简单的操作(只涉及本身数据的简单处理)。DAO 负责将数据转化为实体对象,我们通常用Hibernate来实现。业务逻辑放在service里,每个service方法是一个Transaction Script。AppFuse(http://https://appfuse.dev.java.net/)也是按这种方式架构的。

Martin Fowler更倾向于Domain Model模式(http://www.martinfowler.com/eaaCatalog/domainModel.html)。领域对象不仅是原始数据的对象形式封装,而且包含几乎所有重要的业务逻辑。领域对象可以直接访问数据库,因此DAO不是必要的,而是通过一个他称为 “mapper”的东西提供O/R映射。由于领域对象封装了业务逻辑,service只是一个很薄的facade,提供面向服务的接口。Spring的 JPetstore范例就是按照这种方式架构的。但是,JPetstore的业务逻辑相当简单,我草草看了一下,似乎领域对象没有直接操作持久化逻辑,因此这并不是一个很有说服力的例子。或许更有说服力的是Pluto(http://jakarta.apache.org/pluto/)。在Pluto这里,需要持久化的大多是些配置信息(包括portal本身的配置、portlet的配置和用户配置),它并没有使用DAO,而是直接通过XML binding持久化到XML文件。



potian 写道
在Web应用程序中出现很多疑惑的原因是HTTP的无状态,在一个桌面应用程序中,我们可以一直在客户代码中持有这个领域对象,不断地对这个对象进行操作,但在Web应用程序中,这个就很难,因此通常必须依赖service来进行“一记头“的操作。传递的数据通常是简单的数据类型或者是简单的数据类型组装起来的Bean对象


potian 写道
第一种方式下,所有的工作必须经过Service,好处是接口简单,很少需要直接和背后的对象打交道,但坏处则是Service代码经常需要随着客户界面或行为的变化而变化,去实现新的接口方法,新的对象交互方式

第二种方式,service的主要作用是能够新建和获取(包括查询方法,通常用来统计和查询跨领域对象的数据和对象
)到领域对象,绝大部分的操作度直接在领域对象上进行,坏处是需要知道很多后面对象的接口和行为,但好处是客户端的变化通常不影响到服务端的变化。


potian 写道
有了这些基本的前提,就可以看到,如果领域模型并不复杂,基本的操作都是CRUD,那么我们可能会选择第一种方式,因为这个时候Service不会复杂到无法起到Service本身作用的程度,这个时候也没有多少东西可以复用。复用是以行为为核心,而不是状态。

如果是领域模型比较复杂,那么完全通过Service就可能导致Service非常复杂,这个时候Service层就作用不大了。我们会提供一些简洁的Service方法来获取不同的领域对象,然后把领域对象直接暴露给客户端使用。


potian 写道
模型的复杂性是问题域本身决定的,而不应该有采用何种体系结构来决定。
至于模型的持久有谁来负责还是由模型的复杂性来决定的,一个简单的没有多少行为的模型实际上就是一个实体模型(关系模型),它们的持久可以通过O/Rmapping(或者JDBC)完全解决,因此我们可以通过DAO完全解决掉,这样就成了Service->Manager->DAO->Entity(这时候领域对象退化为简单的实体对象),但是如果简单的关系模型不足以反映领域内部对象的复杂性,那么这些领域对象还需要依赖于多个管理其他领域对象的Manager来处理。在这种情况下,出于对方便利用IOC考虑,一般并不是在领域对象内部持有这个Manager属性,而是领域对象的某些方法里面可能会有Manager这样的参数,它们来自IOC到 Service的注入,或者来自客户端通过IOC容器得到,然后显式地在调用该方法的时候传入。


上面讨论的基本观点就是采用何种模型应该视问题域而定,没有一个确定的标准。

我谈一下个人的看法:我仍然不认为应该在实体类(或者说叫做领域对象)中持有丰富的业务逻辑,即使是在第二种应用场景中,我仍然倾向使用一组面向不同职责的细颗粒度POJO来实现,而不是一个粗大颗粒度的单类来实现,即不同意把业务逻辑放在实体类中来。
0 请登录后投票
   发表时间:2005-03-22  
设计
按照渐进式设计的原则,数据库属于实现细节,我们开始不必考虑,所以CRUD的操作,不会出现在开始的设计中,最开始的设计只关注业务逻辑本身,从需求中抽象出一系列的对象来创建对象模型。在抽象的过程中是要遵循设计的基本原则的,只有对象既有数据又有行为的时候才更容易的应用这些原则来进行设计,对于只有数据或只有方法的类看上去更像一个面向过程的数据和函数库。
好处大概就是更容易构建出可扩展的,健壮的面向对象的对象模型,当然,这和个人能力有关。当需要进行持久化的时候,对于拥有数据的对象由DAO层去负责持久化的操作,这里不会涉及到任何业务逻辑,业务逻辑的方法也不会影响到实体对象的持久化。

测试
业务逻辑有整个对象模型的协作来实现,每个环节都保证单一的,小粒度的职责,可以很容易的单独测试各个环节,即单元测试,也可以组合起来进行大粒度的单元测试。
容易引入Mock Object进行测试。

由于没有做出过令自己满意的设计,所以这些也就是纸上谈兵吧。其实也无所谓好坏吧,只要在团队能力的控制范围内,做出充分满足用户需求的系统来就OK!
0 请登录后投票
   发表时间:2005-03-22  
robbin 写道

我个人是持第一种模型的观点,但是也很希望听取一下持第二种观点人的意见,了解一下该模型到底有什么实际的好处? 我想,这里搬出什么理论大道理,搬出什么Martin Fowler的话都不是我想听的,我想了解的是第二种模型究竟在实际软件开发中,能够给我们带来什么好处?

好吧,抛开业界的灵魂人物。来探讨具体问题。
好处就在于能够很方便的找到操作数据的方法,避免代码重复,防止非法操作。
用业务对象操作实体对象的方式实现业务逻辑的模式,会有多个业务对象操作同一个对象。
而从被操作的实体对象并不能明显的知道有多少对象操作了它。这就有可能出现代码重复,
特别是多人共同开发的情况下。
而且实体对象是裸露在业务对象面前的数据。对于已经有的
约束实体对象的规则,完全可以绕过,要操作某个实体正常途径是需要经过SecurityManager的检查。
但如果不封装在同一个类中,其实是没法阻止不调用SecurityManager,而直接操作实体对象的操作的。
0 请登录后投票
   发表时间:2005-03-22  
个人觉得这是看待一个问题的方向不同而导致的,如果系统从上面建造,则采用领域对象是优秀的,因为他更符合面向对象的思维。而如果系统从下面建造,则采用POJO的方式是轻松的(当然在这样的情况下我也不同以将业务逻辑的责任丢给POJO来处理!)
BTW:POJO/DAO更如同是对关系型数据库的映射,而坚持领域对象的设计师可能根本不希望看到关系形数据库的影子
0 请登录后投票
   发表时间:2005-03-22  
不是很明白有什么好争论的.

面向不同模块的实现者,自然是关注点分离,职责单一.软件被按照职能分为互相协作的小模块. 这样维护容易,测试也容易.

而从调用者的角度,自然是希望自己的需要全从一个对象过来,简单嘛.

但是两者不矛盾阿.或者说,这种"矛盾"贯穿于任何设计中.

那个叫什么facade的模式不就是干这个的吗?客户要一个简单的调用接口,你就麻烦点,帮助客户按照他想要得样子实现一个facade不就得了?facade负责调用那些细粒度的小模块。

现实生活中也是阿。任何一件复杂点的事情都需要很多人甚至很多部门协作。
比如,网站用b2b来从别的公司得到服务。但是终端用户并不期望知道什么b2b,什么supply chain,什么webservice。它们就希望看见一个网站,能做他们想做的所有事情。

这两者,矛盾吗?
0 请登录后投票
   发表时间:2005-03-22  
如果“实体类应该仅仅保持对象状态,只承担映射数据库表的职责”
那何不直接用一个map来完成呢?
比如相对于Order类,我可以
map.put("id",1);
map.put("customer",customer);//其中customer又一个map
map.put("items",items);//items是个Collection,其中每个元素都是表示Item的map
而且基于这种想法,我一样可以写出一个类似Hibernate的东西
问题是,这个对象应该这么“贫血”吗?
如果像Hibernate实体类的方式,要有一个setItems(Collection items)方法,如果多处用到setItems,我就需要多次组装这个Collection。当然,你可以写一个service类,但有必要多这么一个类吗?
OO就是要自己的事自己做。
当然,我也不同意在Order类上加类似findAllOrders这样的方法,这不属于它的职责。DAO还是应该存在的
0 请登录后投票
   发表时间:2005-03-22  
不同意把业务逻辑放到实体类中去,理由很简单,假设我在设计实体类,那么或许我正在考虑一个东西长什么样,而设计业务逻辑,或许我正在考虑一个东西的操作流程,这是两种不同的思维角度,要求我把他们混合在一起,我做不到。
另外一个原因,按照我的想法,在一个庞大的系统中,业务逻辑的设计者和实体类的设计者未必就是同一个人,两者的混合则非常不利于维护和扩展,而两者分开,那么不同的人就只需要关注他们所在层次的那部分设计了。
0 请登录后投票
   发表时间:2005-03-22  
我觉得不能在POJO上面加上DAO的动作,但是也不能就说POJO上面不能有逻辑。
属于这个POJO本身的逻辑(属性的修改触发和验证)就应该有。
如果涉及到多个POJO则要看这些POJO之间的关系和地位来决定是否加一个Service来模拟业务动作。
另外一点是,无论POJO还是Service都应该躲在application layer后面,由application service来调用,再加上事务,持续化和权限等操作。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics