Value对象应包含多少业务逻辑?
我尊敬的一位导师建议,简单的bean浪费时间,价值对象"必须"包含一些有用的业务逻辑。
另一位代表说,这样的代码很难维护,并且所有业务逻辑都必须外部化。
我意识到这个问题是主观的。无论如何询问都想从更多角度了解答案。
解决方案
我个人的喜好是将所有业务逻辑放入域模型本身,即"真实"域对象中。因此,在创建数据传输对象时,它们大多只是域对象的(不可变)状态表示,因此不包含任何业务逻辑。它们虽然可以包含克隆和比较的方法,但是业务逻辑代码的实质仍保留在域对象中。
这取决于。
哎呀,我只是脱口而出吗?
设计对象时要问的基本问题是:控制对象数据的逻辑在被其他对象使用/消费时会有所不同还是相同?
如果不同的使用领域要求使用不同的逻辑,请对其进行外部化。如果对象行进到相同,则将其与类一起放置。
我们最好将它们称为"传输对象"或者"数据传输对象"(DTO)。
之前,这个相同的j2ee模式被称为"值对象",但他们更改了名称,因为它与此混淆
http://dddcommunity.org/discussion/messageboardarchive/ValueObjects.html
要回答问题,我只会在DTO中使用最少的逻辑,这是出于显示原因而必需的逻辑。
更好的是,如果我们正在谈论基于数据库的Web应用程序,那么我将超越核心的j2ee模式,并使用Hibernate或者Java Persistence API创建一个支持延迟加载关系的域模型,并在视图中使用它。
查看视图中的打开会话。
这样,我们不必编程一组DTO,并且可以在视图/控制器等中使用所有可用的业务逻辑。
科罗斯说了什么。
值对象:=一个小的简单对象,例如金钱或者日期范围,其相等性不基于身份。
DTO:=一个在进程之间传递数据的对象,目的是减少方法调用的次数。
这些是马丁·福勒(Martin Fowler)提出的定义,我想推广它们。
将数据和业务逻辑放在一起的想法是促进封装,并向其他对象公开尽可能少的内部状态。这样,客户端可以依靠接口而不是实现。请参阅"告诉,不要问"的原则和Demeter定律。封装使人们更容易理解数据可能处于的状态,更易于阅读代码,更容易解耦类以及通常更容易进行单元测试。
外部化业务逻辑(通常分为"服务"或者"经理"类)会引起类似"在何处使用此数据?"的问题。和"它可以处于什么状态?"很难回答。它也是包裹在一个对象中的一种程序化思维方式。这可能导致贫血领域模型。
外在化行为并不总是不好的。例如,服务层可以编排域对象,但不承担其状态操纵职责。或者,当我们主要是对可以很好地映射到输入表单的数据库进行读/写操作时,也许我们不需要域模型,也不需要任何麻烦的对象/关系映射开销。
传输对象通常用于通过提供调用层所需的最少状态信息来使体系结构层彼此分离(或者与外部系统分离),而不会暴露任何业务逻辑。
例如,在为视图准备信息时,这可能会很有用:仅向视图提供所需的信息,而无需提供其他任何信息,这样它就可以集中精力于如何显示信息,而不是要显示什么信息。例如,TO可能是多个数据源的集合。
优点之一是视图和域对象是分离的。在JSP中使用域对象会使域更难以重构,并促进滥加使用getter和setter(因此破坏了封装)。
但是,拥有大量传输对象也常常会带来很多重复,这也带来了开销。我参与过的某些项目最终都以TO为主,它们基本上反映了其他域对象(我认为这是反模式)。
我同意Panagiotis的观点:以模式进行的公开会议比使用DTO更好。换句话说,我发现如果从视图层一直向下传输域对象(或者其组合),则应用程序要简单得多。
话虽如此,这很难实现,因为我们将需要使HttpSession与持久层的工作单元一致。然后,我们需要确保所有数据库修改(即创建,更新和删除)都是有意的。换句话说,我们不希望视图层具有域对象,修改字段并保留修改而没有应用程序代码有意保存更改的情况。要处理的另一个重要问题是确保事务语义令人满意。通常,获取和修改一个域对象将在一个事务上下文中进行,使ORM层需要进行新事务并不难。具有挑战性的是一个嵌套事务,我们想在其中将第二个事务上下文包含在第一个打开的事务中。
如果我们不介意研究非Java API如何处理这些问题,那么值得关注一下Rails的Active Record,该记录允许Ruby服务器页面直接与域模型一起使用并遍历其关联。