【问题标题】:Should Enterprise Java entities be dumb?企业 Java 实体应该是愚蠢的吗?
【发布时间】:2010-02-25 10:40:25
【问题描述】:

在我们的旧版 Java EE 应用程序中,有很多值对象 (VO) 类通常只包含 getter 和 setter,可能是 equals()hashCode()。这些(通常)是要保存在持久性存储中的实体。 (作为记录,我们的应用程序没有 EJB - 尽管 可能 将来会发生变化 - 我们使用 Hibernate 来持久化我们的实体。)在 VO 中操作数据的所有业务逻辑都是分开的类(不是 EJB,只是 POJO)。我的 OO 心态讨厌这一点,因为我确实相信给定类的操作应该驻留在同一个类中。所以我有一种重构的冲动,把逻辑移到相关的 VO 中。

我刚刚与一位在 Java EE 方面比我更有经验的同事进行了讨论,他确认愚蠢的实体至少曾经是推荐的方法。然而,他最近也阅读了一些质疑这种立场有效性的意见。

我知道有些问题至少会限制实体类中可以放入的内容:

  • 它不应该直接依赖于数据层(例如,查询代码应该进入单独的 DAO)
  • 如果它直接暴露给更高层或客户端(例如通过 SOAP),则可能需要限制其接口

还有没有更充分的理由将逻辑移动到我的实体中?或者还有其他需要考虑的问题?

【问题讨论】:

  • 实体是画外音吗?

标签: java oop jakarta-ee entity


【解决方案1】:

DTOVO 应该用于传输数据,而不是嵌入逻辑。另一方面,业务对象应该嵌入一些逻辑。我说一些,因为在您在协调涉及多个业务对象的逻辑的服务中放入的内容与您在业务对象本身中放入的内容之间始终存在平衡。业务对象中的典型逻辑可以是验证、字段计算或一次仅影响一个业务对象的其他操作。

请注意,到目前为止我还没有提到实体这个词。持久实体随着 ORM 的普及而普及,现在我们尝试将持久实体同时用作 DTO 业务对象。也就是说,实体本身在层与层之间流动,并包含一些逻辑。

还有什么正当理由不 将逻辑移动到我的实体中?或任何 其他需要考虑的问题?

正如您所指出的,这完全取决于依赖关系和您公开的内容。只要实体是哑的(接近 DTO),它们就可以很容易地被隔离在一个专用的 jar 中,作为 层的 API。您在实体中放入的逻辑越多,就越难做到这一点。注意您公开的内容和依赖的内容(加载类,客户端也需要具有依赖类)。这适用于异常、继承层次结构等。

举个例子,我有一个项目,其中实体有一个用于业务层的方法toXml(...)。因此,实体的客户端依赖于 XML。

但是如果你不太关心层,以及API和实现之间的严格分离,我认为在实体中移动一些逻辑是好的。

编辑

这个问题已经讨论过很多次了,由于没有明确的答案,可能会继续讨论。一些有趣的链接:

【讨论】:

  • 我发现大多数答案都很有用,很难选择一个作为“最好的”。但是,我发现你的最平衡和最有见地,所以你得到了复选标记:-) 谢谢。
  • 是的,经过一年多的重读,我不得不承认 ewernli 在这个话题上绝对知道他的方法。
【解决方案2】:

我认为你的观点是正确的。
查看更多信息 - http://martinfowler.com/bliki/AnemicDomainModel.html
使用 JPA,实体是轻量级对象。所以我认为它们的逻辑没有任何问题。

如果与 SOAP/Web 服务一起使用,我会添加一个单独的外观层。

【讨论】:

  • 同意。使用只有 getter 和 setter 的对象完全违背了 OO,更像是在 C 中使用结构。我喜欢 C,但这是 Java,每次我不得不涉足一个只包含样板代码形式的类时,我都会畏缩getX() 和 setX()
  • 有趣的文章,但我不确定是否说贫血域模型是一种反模式是完全公平的。事实上,它首先带来了什么是模式的问题。从逻辑的角度来看,GOF 模式是有意义的。那么 MVC 是否可以被定义为同义的模式呢?
  • 嗯,GOF 模式不仅优雅,而且解决了程序员迟早会遇到的一系列问题。 MVC 在本质上更具概念性,应用层也是如此,后者通常被称为架构。直截了当地说,没有什么可以证明贫血是合理的。只是域过于丰富会破坏Service层的作用。如果你把它与关于 DAO 是否应该与 ORM 一起存在的辩论结合起来,那么问题是层是否应该存在。 P.S:我既不赞成也不介意向喜欢富域的人学习:)
  • @James P. IMO MVC 也解决了一个具体问题。你可以争论它是一种设计还是一种架构模式——坦率地说,我不太在乎。层在某些情况下也很有用,在其他情况下是多余的。我觉得您正试图将对话引向更一般/哲学的方向,我更愿意留在现场,并在具体背景下专注于我的实际问题。
  • 我认为将 MVC 称为模式的问题在于它实际上是 3 种 GoF 设计模式的组合:观察者、复合和策略。这三种模式的结合形成了“架构”设计模式。
【解决方案3】:

除了前面提到的 Fowler 文章之外,在 Eric Evans 的 Domain Driven Design (2004) 一书中,还有一篇关于富域模型与贫乏域模型的完整论文。

另外,请查看http://dddcommunity.org/

【讨论】:

  • 你真的需要读这本书。这是作品具有开创性和实用性的极少数情况之一。
【解决方案4】:

好的,下面是我从 Java EE 培训师那里得到的反馈摘要。

从实用的角度来看,只要方法与实体的属性一起使用,通过移动逻辑在贫乏领域和丰富领域之间取得折衷应该不是问题。当涉及到一个富域时,必须在某个地方划清界限,显然 Fowler 和 King 都朝那个方向发射了 cmets。

例如,考虑 BankAccount 中的 calculateInterestRate() 方法,该方法从其他域对象获取信息,例如验证某人成为客户的时间。为了避免依赖,可以将方法拆分为对象,但这种方法意味着代码最终可能会散布在多个类中。此时,不妨创建一个 InterestCalculator 类。

要考虑的另一件事是线程安全。 Spring 处理的单例 DAO 和服务应该是线程安全的,而域模型中的任何内容都将面临并发问题。

最后是维护问题。你确定你会在几年后维护应用程序吗?您所做的选择似乎是合理的,但您确定下一位开发人员将具备轻松理解您的代码所需的专业知识吗?

【讨论】:

    【解决方案5】:

    您所指的约定是采用贫血域模型,而不是富域模型,其中实体是简单的 POJO,注释为 bean (@Entity),在 getter 和 setter 方面只有最低限度。因此,具有 toXML() 方法的类将被视为富域。我想这有助于清楚地了解映射到关系数据库的内容,即使粒度不同。

    优点是逻辑和数据之间有明确的分离(这是打破 OO 哲学的地方)。方法是将这些分组到业务逻辑类或服务中。这是根据分层架构,各个层分别是域、服务、DAO 和 UI。

    这就是理论。

    编辑:为了澄清一下,当我说逻辑和数据之间有明确的区别时,我的意思是一个对象是面向数据的,另一个是面向方法的,这可以被认为是回归程序的方式。

    【讨论】:

    • @James P. 如果您因此认为 OO 被“破坏”,恐怕您不太了解 OO 的意义:-(
    • 这是可能的,我总是乐于接受启蒙;)。如果我错了,请纠正我,但听起来封装原则是这里辩论的中心。
    • P.我不确定我是否了解您的目标。无论如何,我不想就 OO 进行“辩论”,因为 a) 我更愿意让这个话题成为焦点,b) 关于 OO 的观点(正确地)是主观的和上下文相关的,c) 600 个字符对此是不够的无论如何。
    • 我已经发布了第二个答案,其中包含联系人就该问题向我提供的一些反馈的摘要。
    【解决方案6】:

    向这些类添加逻辑的主要问题是,它们需要更多属性来跟踪对象状态,而这些额外的属性通常不需要序列化。这意味着这些类的序列化机制需要额外的工作。

    考虑到许多项目混合了 jr.程序员和sr。程序员和大部分工作是由不了解(或不关心)最佳序列化的 jr 执行的,将这些普通的旧 java 对象作为“值对象”要容易得多,它们几乎只是传递和接收数据和把逻辑放在其他地方。

    如果您设法拥有一个将逻辑放置在业务对象中的架构(即 VO + Logic),我认为这也会更好。请记住,整个团队都在同一个页面上,他们不会重复代码和逻辑(这永远不会发生,对吧?)

    简短的回答:不,他们不应该总是愚蠢的,但这样更容易让他们变得愚蠢。

    【讨论】:

    • 如果序列化是一个问题,您可以将附加属性设置为瞬态。没什么大不了的。
    • @Matthew 当然可以,只要新手明白你在说什么。
    【解决方案7】:

    我已经完成了一些 C 编程,虽然 OOP 很适合通过构建类层次结构来简化问题,但我仍然发现简单的 C 方法在许多情况下是最简单和最伟大的。在 C 中,我们有只有公共成员的结构,程序员将这些结构传递给函数(在 Java 中,这样的函数将是某些实用程序类中的静态函数),这些函数操纵成员。数据和算法是分开的,因为函数不是结构的成员。我一直觉得 VO 对象就像 C 中的结构体。

    在很多情况下 C 语言不是最好的,因为例如没有层次结构,没有多态性,这些都是体面的 OOP 程序员觉得有用的东西。但是总的来说,我喜欢这种简单的 C 方法并且更喜欢使用它,除非我知道 OOP 技术会非常有益。例如。当我需要使用类的层次结构来建模某些东西,或者我需要确保一个或多个类的成员(在层次结构中)总是相互一致时,我不能使用 C 结构方法。但在这些情况下,无论如何我不仅会有 setter 和 getter。

    我也会参考这篇关于 C++ 的文章,但我喜欢这个人解释这些事情的方式: http://www.gotw.ca/gotw/084.htm 本文有 2 条关于何时将函数设为类成员的规则:

    (从下面的引用中我省略了一些东西,如果你想看全部,请阅读原文)

    1. 如果必须是成员,则始终使其成为成员:哪些操作必须是成员,因为 C++ 语言只是这样说(例如,构造函数)或由于功能原因(例如,它们必须是虚拟的)?如果他们必须是,那么哦,好吧,他们只是必须是;结案。

    2. 如果它需要访问内部信息,则更愿意使其成为成员:哪些操作需要访问内部数据,否则我们必须通过友谊授予?这些通常应该是成员。

    在所有其他情况下,更愿意将其设为非成员非朋友:哪些操作可以与非成员非朋友同样有效?这些可以,因此通常应该是非成员。这应该是要争取的默认情况。

    我的感觉是,如果您不确定是否要向这些类添加函数,那么您并没有真正需要这样做。我在这里写的只是我不向此类类添加方法的所有原因的一部分,但也许这是我所有其他原因的根源。但这都是主观的,所以 YMMV。顺便说一句,静态实用函数方法在大多数情况下使单元测试变得简单。

    【讨论】:

      【解决方案8】:

      经常生成实体。在像 Java 这样的语言中没有分部类这样的东西,所以我们不能扩展实体(不考虑像访问者这样的模式)。所以当你需要重新生成实体时,你必须再次添加业务逻辑,这根本不实用。

      对于实体,我宁愿在实体及其逻辑之间进行high cohesion 设计。

      需要考虑的其他问题是,并非实体的所有业务运营在所有情况下都是平等的。此外,允许实体中的业务逻辑倾向于使实体与 XML 等集成技术耦合,在我看来,这些技术应该始终远离域逻辑。在洋葱架构中,XML 位于外壳中,域对象位于内部,以说明如果您想创建一个可重用的可插拔系统,它们之间的距离究竟有多远。

      【讨论】:

        猜你喜欢
        • 2011-06-09
        • 1970-01-01
        • 2010-09-18
        • 2016-03-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-07-07
        相关资源
        最近更新 更多