15

刚刚读完Greg Young 的这篇文章,他在其中谈论 Microsoft 推荐带有哑数据传输对象的模式。他暗示,在 Java 社区中,事情正朝着另一个方向发展。

我的问题是您的实体对象中应该有多少逻辑?我工作的地方(C# 商店)的理念是,如果你不能序列化它,就不要把它放在实体中。

4

5 回答 5

17

马特,

我会说你的商店正在编写程序代码。我想明确一点,许多大型系统(包括我曾经参与过的许多系统)都是使用过程代码编写的,这并没有错。它有一个时间和地点。

现在程序代码在域模型中没有位置。如果您想使用更程序化的样式,但可以将其与表模块或 Active Record 模式一起使用。我认为在指导中具有如此破坏性的不是缺少 OO,而是使用具有过程逻辑的域模型。

这导致人们花费大量资源来构建领域层(阻抗不匹配、构建聚合的思考过程时间、隔离、无处不在的语言等),而没有获得领域层(通常是可维护性)将提供的任何好处。换句话说,虽然您可以很好地满足您的功能要求,但您最终会花费大量预算而几乎没有回报。

现在回到什么是“行为”,我想从面向对象而不是“领域驱动设计”的观点来关注这个问题。一个对象通常会封装一些状态并且通常会暴露一些行为。

快速重申:封装状态,暴露行为

那么一个对象应该有哪些行为呢?简而言之,它应该是对其封装的状态进行操作的行为。在理想的行为 OO 世界中,状态永远不会从仅对象行为中暴露出来。如果我们开始看到如下代码,请从战术上将其放入代码中:

Customer c = GetCustomerFromRepository();
c.Status = CustomerStatuses.Deleted;
c.LastUpdated = DateTime.Now;
c.UpdatedBy = GetCurrentUser();
CustomerRepository.Save(c);

我们有一个 SRP 违规......此代码是应该是客户对象的行为的代码,因为客户对象的“责任”是。

封装有关客户的状态并公开行为。

因此,我们可以看到最好有一个 Customer.Delete() 方法。(是的,这是我知道的一个坏例子......)

现在我们也可以通过使用 TDD 来解决这个问题。对我们来说,处理行为提供的接缝比处理所有状态都暴露的接缝要容易得多。这样做的原因是我不需要在我的测试中复制逻辑。客户端代码不关心删除是如何工作的……它只关心客户公开行为。因此,在我们的测试中,我们不会断言 c.State == CustomerStates.Deleted 和 c.UpdatedBy==GetCurrentUser() 等,我们会简单地断言 delete 方法是通过使用模拟在客户接缝上调用的。

现在回到标题。业务对象中应该包含的逻辑量​​是由其负责封装其状态的逻辑量。有时这很多,有时不是。有些地方你也想使用服务......一个很好的例子是协调许多域对象之间的交互以实现给定的行为,但即使在这里,服务也应该调用域对象上的行为

这有助于澄清一些事情吗?

格雷格

于 2009-01-23T18:56:07.390 回答
4

如果您称它们为“域模型对象”,那么我将假设您指的是 Fowler 的域模型模式。http://martinfowler.com/eaaCatalog/domainModel.html

鉴于该假设,那么您问题的答案是“所有业务逻辑”,因为这本质上是模式的定义。

不幸的是,“域模型”一词最近似乎被淡化为仅表示您的数据的对象模型而没有行为。

如果您还没有这样做,我会鼓励您阅读 PoEAA 并确定您认为域逻辑在您的情况下属于何处。如果您决定使用域模型,那么我鼓励您阅读 Evan 的 DDD 书并了解实体、值对象和服务之间的区别。

希望有帮助!

于 2009-01-23T19:28:24.017 回答
2

最近,我一直在玩弄创建具有结构的域模型的想法,并且只有那些对该模型通用的行为(即可以跨多个有界上下文使用的行为),以及针对有界上下文特定行为的扩展方法. 这使域模型接近于 DTO(对于那些喜欢那样的人),并将该域模型的使用限制为仅在有界上下文中允许的行为。因此,这可能是一种中间路线响应的选择。:)

于 2009-01-23T19:25:39.873 回答
1

重点是如何定义逻辑。举一些例子:

  1. 我不会将函数 getFullName() 归类为 Person 实体,它只是连接一些字符串,作为逻辑。
  2. 计算订单项目值更有可能符合逻辑。
  3. 做一些预订交易我肯定会说是合乎逻辑的。

第 1 点和第 2 点可能会让我进入实体。第3点不是。所以我将逻辑定义为:

  • 任何与持久性相关的操作(读/写)
  • 涉及任何其他(不直接相关,例如主从)实体的任何操作

IMO,任何这些操作都不属于实体。

现在,为什么/何时我不会将第 1 点和第 2 点类型的操作也放入实体中?这是一种相当罕见的情况,但我不会这样做,因为存储在实体中的数据需要以某种方式解释才能被应用程序使用(例如,如果取决于当前用户,字段 X 的内容有不同的含义),这意味着实体的数据本身会产生一些逻辑。

于 2009-01-23T18:29:42.717 回答
0

据我了解,与实体相关的所有业务逻辑都应该进入该实体。这包括根据系统的业务规则定义实体的行为或内部结构的任何逻辑。这不应该包括表示逻辑或持久性逻辑(Active Record 设计模式的明显例外),但应该包括诸如数据验证、实体关系、状态机和其他定义实体如何根据实际行为的东西——它试图建模的世界事物。

我尝试查看它的方式是尝试使我的模型尽可能地可重复使用。如果将模型移植到客户端代码(或使用实体的代码)可能不同的不同系统,请始终尝试考虑如何使用模型。如果功能不是实体的一部分,它是否仍会按照相同的业务规则以相同的方式运行?如果答案是否定的,那么该功能应该在实体中。

于 2009-01-23T18:41:34.143 回答