论坛首页 Java企业应用论坛

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

浏览 67214 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (1)
作者 正文
   发表时间:2005-03-23  
robbin 写道
希望大家讨论都结合自己的实践经验来谈论,不要高来高去的堆砌专业词汇,不要搞成一场引经据典的语文修辞比赛!

在这里我们不相信权威,我们只相信实践


我只是顺带提到这个不算吧,再说我觉得权威不权威在其次,重要的是正确,在3种模式上人家分析透彻多乐(注:只论对3种模式的分析,不论其对3种模式的偏好)。

我认为,现在普遍采用第一种模式的原因至少有两点:
1、它可以让我们达到目标,并且有成功经验
2、它的成本低(这个成本包括学习、资源等等)
0 请登录后投票
   发表时间:2005-03-23  
看看这个例子:
0 请登录后投票
   发表时间:2005-03-23  
我觉得是我们没有碰到过一些复杂程度和设计要求高的项目,使我们的经验大多数停留在第一种模式上面。
我很是期盼去做做其他两种模式,去亲身体验一下。这样才会得到最真实地感受和经验。
0 请登录后投票
   发表时间:2005-03-23  
做java游戏开发吧,可以深刻理解OOD。举个例子:重力怎么实现?动物是只知道往上跳的,可是地球会把一切有质量的物体拉下来
0 请登录后投票
   发表时间:2005-03-23  
七彩狼 写道
昨天就看到了 “Hibernate实体类 == 领域模型 ? ” 这篇帖子,http://forum.iteye.com/viewtopic.php?t=11608&highlight= 。 看到今天的题目,就更明确了。



首先,表明一下立场,我是第二种方式的支持者,也就是说“实体类”里应该加入业务逻辑。



“实体类”这个称谓,感觉不是很好,因为它实际已经限制住了它的作用范围,做为“实体”,它更应该是和数据库相关的,更应该是没有业务逻辑的。MartinFowler所说的DomainModel,不应该是所谓的“实体”,DM更应该是业务层中的一部分。



举一个具体的例子吧,看看大家如何解决,无论是用 DM 还是用 实体:



在财务和金融行业的业务中,最常见的一个概念,就是金额,它几乎会出现在任何一张业务单据中,而金额这个概念,除了具体数值外,还会存在汇率,原币,本币,外币金额等相关数据。而对于金额的业务操作则会涉及到 加, 减 ,求平均值,金额折算 等相关的一些业务操作。


金额 : { 发生金额 , 发生币种 , 原币金额 , 原币币种 , 本币金额 , 折原汇率 , 折本汇率 }


由于金额几乎在每种类型的业务单据中都会出现,因此,也就是说,几乎每张业务单据的数据表上,都会存在着 上面所罗列的若干字段,甚至是倍数。 如果是用 实体 + Service 的方式,就一定会写一个 金额 Service 的这个东西。 如果纯粹从面向对象的角度来讲,这究竟是不是指责单一呢?我不觉得是,这让我仿佛又回到了 C 的年代, 数据 + 过程。


由于实际业务中, 金额 可能并不一定会有 上面所有的字段,也或者可能更多,因此,金额 实际也会有自身的集成结构体系的。


比如 : 在 分账财务 中 , 金额 : { 发生金额 ,发生币种 , 原币金额 ,原币币种 , 折原汇率 }
在 统账财务 中 , 金额 : { 发生金额 , 发生币种 , 原币金额 , 原币币种 , 本币金额 , 折原汇率 , 折本汇率 }
在 非核算业务 中, 金额 : {发生金额 , 发生币种}

对应这这种继承体系,同样,我们也要维护一套 金额 Service 的继承体系,这是否也是职责单一呢?是否更利于维护和扩展呢?


在不贫血的DM中,金额可以被认为是一种业务的元数据,具体的实现则可以表示为值对象(ValueObject),就像是 Numberic / Integer / BigDecimal 这样的结构一样。在分析模式或者其它的一些经典的业务建模书籍中,都可以找到具体的设计和实现。

如果把 BigDecimal 这样的类,也分为 数据实体和Service ,是否有些感觉诡异呢?



大概这样吧,不知道说的清楚不清楚,我只想通过这个例子表明,DomainModel 或者可以表示为 实体 + Service ,但这觉不是 DomainModel 最佳的实现方式,同时这也不是符合上面大家都在提到的职责单一原则的,恰恰相反,它破坏了对一个整体概念的封装。


显然你的理解有误。拿你举的这个金额的例子来说吧。如果是第一种模型,我会建模为一套继承体系结构的实体类,和一个业务逻辑处理类,根本不会出现你所谓的一堆Service的情况;而第二种模型中,则只会有一个类,所有的数据和逻辑都在这一个类中处理。在这种情况下,作为金额这样一种情况来说,用一个单类你就无法表达它的多态继承的内在特征。
0 请登录后投票
   发表时间:2005-03-23  
firebody 写道
partech 写道
robbin 写道

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

好吧,抛开业界的灵魂人物。来探讨具体问题。
好处就在于能够很方便的找到操作数据的方法,避免代码重复,防止非法操作。
用业务对象操作实体对象的方式实现业务逻辑的模式,会有多个业务对象操作同一个对象。
而从被操作的实体对象并不能明显的知道有多少对象操作了它。这就有可能出现代码重复,
特别是多人共同开发的情况下。
而且实体对象是裸露在业务对象面前的数据。对于已经有的
约束实体对象的规则,完全可以绕过,要操作某个实体正常途径是需要经过SecurityManager的检查。
但如果不封装在同一个类中,其实是没法阻止不调用SecurityManager,而直接操作实体对象的操作的

很精辟! 第一种方式,很容易陷入过程式的开发。
不过现在国内绝大多数的应用都是Transaction Script 模式的,这个本应该是缺点的东西反而已经逐渐变为“优点”了。 正如Robbin说的“分离关注点”,不过和 在Action或者Service中,为实现某一业务逻辑,频繁调用不同Service暴露的方法,这也不应该成为分离关注点的结果吧! 更甚的是,某几个业务逻辑 可能 一部分代码是重复的,这似乎也是顺期而然的事情!    


我上面不是刚说完吗,业务逻辑的复用是另外一个关注点,可以通过其他机制很好的解决。任何一种模式都必然有其优缺点,如果你能够发挥其优点并克服其缺点那这种模式当然是不二之选,空谈模式的优缺点好象根本就没任何意义。
0 请登录后投票
   发表时间:2005-03-23  
Archie 写道
看看这个例子:


老实说,我觉得你们对第一种模型(即所谓贫血模型)理解有误,你们认为实体类就是只有getter/setter方法的数据类,这是误解。就你画出来的这个UML类图来说,即使在第一种模型中,这些方法也是必须放在实体类内部的。
0 请登录后投票
   发表时间:2005-03-23  
还是拿代码出来说话比较清楚一些:

举一个例子, 在某个Xdon论坛, 假设有Forum和Topic这样2个实体类(1:n的关系) :
class Forum {
    List topics;
    int timeToLive;
}

class Topic {
    Forum forum;
    Date lastUpdatedTime;
}

timeToLive是斑竹设置的允许回复的时间期限, 一旦某个帖子的最后更新时间已经过了这个期限, 就不允许回复了 (好像比robbin大大过了期限就删除的规则更加人性一些, hehe)
比如"X阔X空"论坛设置的是30天, 比如"XXX企业婴用"设置的是60天

逻辑也不复杂:
判断一下Topic t, 如果满足条件: t.forum.timeToLive + t.lastUpdatedTime < now 就不允许回复了.

按照实体类里面不应该有业务逻辑的人观念, 代码是写在TopicManager里面的:
class TopicManager {
    boolean isAllowReply(Topic t); {
        Calendar dueDate = Calendar.getInstance();;
        dueDate.setTime(t.lastUpdatedTime);;
        dueDate.add(Calendar.DATE, t.forum.timeToLive);;
    
        Date now = new Date();;
        return now.after(dueDate.getTime(););;
    }
}


而对于第2种观点, 这种代码明显是属于Topic这个实体对象内部, 应该放到它里面:
class Topic {
    boolean isAllowReply(); {
        Calendar dueDate = Calendar.getInstance();;
        dueDate.setTime(lastUpdatedTime);;
        dueDate.add(Calendar.DATE, forum.timeToLive);;
    
        Date now = new Date();;
        return now.after(dueDate.getTime(););;
    }
}


除了封装好一些以外, 除了少了一个TopicManager类以外, 第2种做法还有其他优势么? 难道这个就是鼓吹得很凶的DDD么?
很明显, 答案是NO

当这种时间类型的判断, 计算在系统里面逐步地增加, 偶们会发现其实代码都是很类似的: java.util.Date/Calendar 以及int/double这种东西充斥着method body.
这才是DDD出手的时候: (请参考DDD里面关于Duration/TimePoint/TimeInterval的章节)
class Forum {
    List topics;
    Duration timeToLive;
}
class Topic {
    Forum forum;
    TimePoint lastUpdatedTime;
    boolean isAllowReply(); {
        return TimePoint.now();.isAfter(timeToLive.plus(gracePeriod););;
    }
}


问题来了: 这个时候Forum和Topic能作为实体对象么, 将他们的Duration/TimePoint属性映射到数据库么?
答案是Yes: 有了Hibernate这种可以武装到牙齿的工具, 实体类不是以前那种只有简单属性(String/Number/Date), 不是以前那种简单的Table <-> 纯数据类型对象映射了
一个小小的例子, 请参考 http://forum.iteye.com/viewtopic.php?t=9035

其实废话了那么多, 偶想表达的观点是: 将业务逻辑放入实体类是可行的,也是必要的, 但不是说把原先在Service/Manager的代码移入了实体类就是DDD了.
至于什么是DDD? 偶没有太多的实际经验, 除了上面那个Time以外, 偶只实践过关于金额的DDD, 偶接触的大部分MIS项目确实用Transaction Script就能很好完成了.
还是有请哪位实践经验丰富的DDD大大来点拨一下了......
0 请登录后投票
   发表时间:2005-03-23  
robbin 写道
Archie 写道
看看这个例子:


老实说,我觉得你们对第一种模型(即所谓贫血模型)理解有误,你们认为实体类就是只有getter/setter方法的数据类,这是误解。就你画出来的这个UML类图来说,即使在第一种模型中,这些方法也是必须放在实体类内部的。


呵呵,不统一出发点,咋讨论啊。举个例子对比一下贫血模型和非贫血模型吧。
0 请登录后投票
   发表时间:2005-03-23  
请注意理解我帖子的内容:丰富业务逻辑语义

第一种模型,即不带丰富业务逻辑语义的实体类并不是只有getter/setter的单纯数据类,通过上面你们举的两个例子我看明白了,实际上你们上面攻击第一种模型目标瞄准错误了,你们攻击的是单纯数据类,而不是实体类。

实体类并不是不带任何逻辑的,它也必须带有和状态相关的逻辑。第一种模型和第二种模型的共同点在于他们都会带有逻辑,区别在于带有什么样的业务逻辑。

在第一种模型中,实体类带有的逻辑是不涉及持久化的,在第二种模型中,实体类带有的逻辑需要依赖持久化,这才是真正的区别。

通过上面的讨论,我们知道不管哪种模型,都应该把持久化操作剥离出去,在这种情况下:
第一种模型的依赖关系是
business object --&gt; DAO --&gt; persistent object(实体类)
第二种模型的依赖关系是
domain object(including business logic) &lt;---&gt; DAO

从上面的依赖关系图可以看出来,第二种模型实际是把第一种模型的业务对象和持久对象合并为一个了。在第一种模型中的实体类它也会带有逻辑,但是他不会带有必须依赖DAO才能够完成的逻辑。在第一种模型中,一种行为究竟应该划分到业务对象层,还是应该划分给实体类,判断的依据就是该行为的实现是否需要依赖DAO,如果需要依赖,那么就放到业务对象去,如果不需要依赖,就放在实体类里面。

好了,明确了这一点,大家继续讨论吧。
0 请登录后投票
论坛首页 Java企业应用版

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