在我们的旧Java EE应用程序中,有许多值对象(VO)类,它们通常只包含getter和setter,可能是equals()
和hashCode()
。这些(通常)是要在持久性存储中保存的实体。 (为了记录,我们的应用程序没有EJB - 虽然可能将来发生变化 - 我们使用Hibernate来持久化我们的实体。)操作VO中数据的所有业务逻辑都是分开的类(不是EJB,只是POJO)。我的OO心态讨厌这个,因为我确实认为给定类的操作应该驻留在同一个类中。所以我强烈要求重构将逻辑转移到相关的VO中。
我刚刚与一位在Java EE方面比我更有经验的同事进行了讨论,并且他确认愚蠢的实体至少曾经是推荐的方式。不过,他最近也阅读了有关这一立场有效性的意见。
我知道有些问题至少限制了实体类中的内容:
是否有更正确的理由不将逻辑移入我的实体?还是要考虑其他任何问题?
答案 0 :(得分:19)
DTO 和 VO 应该用于传输数据,而不是嵌入逻辑。另一方面,业务对象应该嵌入一些逻辑。我说一些,因为在服务中放置的内容之间总是存在平衡,这些服务涉及涉及多个业务对象的逻辑和放在业务对象本身中的内容。业务对象中的典型逻辑可以是验证,字段计算或一次只影响一个业务对象的其他操作。
请注意,到目前为止,我还没有提到过 entity 这个词。使用ORM推广持久化实体,我们现在尝试同时使用持久化实体作为DTO 和业务对象。也就是说,实体本身在层和层之间流动,并包含一些逻辑。
还有其他正当理由吗? 将逻辑移入我的实体?或任何 还需要考虑其他问题吗?
正如您所指出的,这完全取决于依赖性和您所揭露的内容。只要实体是哑巴(靠近DTO),它们就可以很容易地被隔离在一个专用的jar中,作为层的API 。你在实体中投入的逻辑越多,就越难做到这一点。注意你暴露的内容和你所依赖的内容(类的负载,客户端也需要拥有依赖类)。这适用于异常,继承层次结构等。
举一个例子,我有一个项目,其中实体在业务层中使用了方法toXml(...)
。因此,实体的客户端依赖于XML。
但如果你不太关心图层,以及API和实现之间的严格分离,我认为在实体中移动一些逻辑是好的。
修改强>
这个问题已经讨论很多次了,可能会继续讨论,因为没有明确的答案。一些有趣的链接:
答案 1 :(得分:8)
我认为你的观点是有效的。
有关详情,请参阅此内容 - http://martinfowler.com/bliki/AnemicDomainModel.html
使用JPA,实体是轻量级对象。所以我认为它没有任何问题,因为它们具有逻辑性。
如果使用SOAP / Web服务,我会添加一个单独的Facade Layer。
答案 2 :(得分:4)
除了前面提到的Fowler文章之外,Eric Evans的书 Domain Driven Design (2004)中有一篇关于富域与贫血域模型的完整论文。
另外,请查看http://dddcommunity.org/
答案 3 :(得分:3)
是的,这是我从Java EE培训师那里得到的反馈摘要。
从实际的角度来看,只要方法与实体的属性一起工作,通过移动逻辑在贫富域之间达成妥协就不应该成为问题。当涉及到一个丰富的领域时,必须在某个地方画出这条线,显然Fowler和King都在那个方向发出了评论。
例如,考虑BankAccount中的calculateInterestRate()方法,该方法从其他域对象中获取信息,例如验证某人已成为客户端的时间。为了避免依赖,可以在对象之间拆分方法,但这种方法意味着代码可以在几个类中结束。此时,还可以制作一个InterestCalculator类。
要考虑的另一件事是线程安全。 Spring处理的Singleton DAO和服务应该是线程安全的,而域模型中的任何东西都会暴露于并发问题。
最后,还有维护问题。你确定你会在几年内维持申请吗?您做出的选择似乎是合理的,但您确定下一个开发人员将具备轻松理解您的代码所需的专业知识吗?
答案 4 :(得分:1)
您所指的约定是采用贫血域模型,而不是富域模型,其中实体是简单的POJO注释为bean(@Entity),在getter和setter方面只有最低限度。因此,使用toXML()方法的类将被视为富域。我想,即使粒度不同,有助于清楚地了解映射到关系数据库的内容。
优点是逻辑和数据之间存在明显的分离(这是OO理念被打破的地方)。方法是将这些分组为业务逻辑类或服务。这是根据分层架构,其各自的层是域,服务,DAO和UI。
这就是理论。
编辑:只是为了澄清一下,当我说逻辑与数据之间存在明显的分离时,我的意思是一个对象是面向数据的,另一个是面向方法的,可以被认为是一个回程到程序的过程。
答案 5 :(得分:1)
向这些类添加逻辑的主要问题是,它们需要更多属性来跟踪对象状态,而这些额外属性通常不需要序列化。这意味着这些类的序列化机制需要额外的工作。
考虑到许多项目混合了jr。程序员和sr。程序员和大部分工作是由不了解(或关心)最佳序列化的jr执行的,将这个普通的旧java对象作为“值对象”更容易,它几乎只是传递和接收数据将逻辑放在其他地方。
如果您设法将逻辑放置在业务对象(即VO + Logic)中,我认为这也会更好。请记住,整个团队都处于同一页面,他们不会重复代码和逻辑(这种情况永远不会发生吗?)
简短的回答:不,他们不应该总是愚蠢,但更容易让他们这样。
答案 6 :(得分:0)
我已经完成了一些C编程,虽然OOP很适合通过构建类层次结构来简化问题,但我仍然发现简单的C方法在许多情况下是最简单和最好的。在C中,我们只使用公共成员进行结构化,并且程序员将这些结构传递给函数(在Java中,这些函数将是例如某些实用程序类中的静态函数),其函数操纵成员。数据和算法是分开的,因为函数不是结构的成员。我总觉得VO对象就像C中的结构一样。
有很多情况下C语言不是最好的,因为例如没有层次结构,没有多态性,那些体面的OOP程序员发现有用的东西。但总的来说,我喜欢这种简单的C方法,并且更喜欢使用它,除非我知道OOP技术真的很有用。例如。当我需要使用类的层次结构来建模或者我需要确保一个或多个类的成员(在层次结构中)总是彼此一致时,我就不能使用C结构方法。但在这些情况下,我不仅会有制定者和吸气者。
我也会参考这篇关于C ++的文章,但我喜欢这个人如何解释这些事情: http://www.gotw.ca/gotw/084.htm 本文有两个关于何时使函数成为类成员的规则:
(从下面的引文中我遗漏了一些东西,如果你想看到所有内容,请阅读原文)
如果它必须是一个,那么总是把它变成一个成员:哪些操作必须是成员,因为C ++语言只是这样说(例如,构造函数)或者由于功能原因(例如,它们必须是虚拟的)?如果他们必须,那么哦,他们只需要;案件结案。
- 醇>
如果需要访问内部,请更喜欢将其设为成员:哪些操作需要访问我们原本必须通过友谊授予的内部数据?这些通常应该是成员。
在所有其他情况下,更喜欢让它成为非成员非朋友:哪些操作可以像非成员非朋友一样工作?这些可以,因此通常应该是非成员。这应该是争取的默认案例。
我觉得如果你对是否在这些类中添加函数有疑问,你就没有真正的需要。我在这里写的只是为什么我不向这些类添加方法的所有原因的一部分,但也许这是我所有其他原因的父亲。但这对YMMV来说都是主观的。 BTW静态效用函数方法在大多数情况下使单元测试变得简单。
答案 7 :(得分:0)
经常生成实体。在像Java这样的语言中,没有部分类这样的东西,所以我们不能扩展实体(不考虑像Visitor这样的模式)。因此,当您需要重新生成实体时,您将不得不再次添加业务逻辑,这根本不可行。
对于实体,我宁愿在实体及其逻辑之间进行high cohesion设计。
需要考虑的其他问题是,并非实体上的所有业务运营在所有情况下都是平等的。此外,允许实体中的业务逻辑倾向于使实体与XML等集成技术相结合,在我看来,这应该始终远离域逻辑。在洋葱体系结构中,XML将位于外部shell和内部的域对象中,以说明如果要创建可重用的可插入系统,它们之间的实际距离是多远。