【问题标题】:Can Entity be identified by all of its properties?实体可以通过其所有属性来识别吗?
【发布时间】:2017-02-20 11:37:24
【问题描述】:

问题是 - 实体可以由它的所有属性或仅由它的 Id 定义。

这是一个例子:

class Wallet{
    int id;

    Map<BillType, Bill> bills;  //can have only 1 bill per bill type

    void addBill(BillType billType, Bill bill){
        this.bills.put(billType, bill);
    }
}

//this is probably an Entity, since it is mutable, but it has no global Id (only local bound to wallet)
//and equality is based on all of the properties
class Bill{
    BillType billType;
    Map<TypeOfBillSide, SideOfBill> billSides;  //can have only front or back

    Bill(BillType billType){
        this.billType = billType;
    }

    void drawWithPenOnBillSide(TypeOfBillSide typeOfBillSide, String drawing){
        this.billSides.get(typeOfBillSide).drawWithPenOnBillSide(drawing);
    }

    void burn(){
        System.out.println("I burned this bill");
    }
}

//this is probably an Entity, since it is mutable, but it has no global Id (only local bound to Bill)
//and equality is based on all of the properties
class SideOfBill{
    TypeOfBillSide typeOfBillSide;
    String drawing;

    public SideOfBill(TypeOfBillSide typeOfBillSide) {
        this.typeOfBillSide = typeOfBillSide;
    }

    void drawWithPenOnBillSide(String drawing){
        this.drawing = drawing;
        System.out.println("I draw on this side " + this.drawing);
    }
}

enum BillType{
    DOLLAR_10,
    DOLLAR_20,
    DOLLAR_50;
}

enum TypeOfBillSide{
    FRONT_SIDE,
    BACK_SIDE
}

这里我有一个全球唯一的钱包——即聚合根。它有账单,我认为在这种情况下是实体(因为我可以更改账单的状态,但钱包中的账单仍然是该账单)。状态可以通过在票据的任何一侧绘制一些字符串来改变(SideOfBill - 在这种情况下也是一个实体)。

票据本身仅作为钱包的一部分才有意义,而且在钱包中我只能有一种票据(我不能有 2 张 10 美元的钞票,例如只有一张)。

如果我将其视为 Value 对象,并使其不可变,那么每次我在 Bill 上绘制一些东西时,我都必须制作新的 bill——在这种情况下,这有点奇怪,而且在代码中也很难做到。

如果我将其视为全球唯一的实体,我将必须拥有 Bill 的 ID,它实际上是由 [Wallet.Id & Bill.billType] 合成的。但在这种情况下,Wallet.id 并不自然适合 Bill 类。

最自然的事情是,我将 Bill 视为 Entity 并具有用于测试所有 Bill 属性的 equals 方法(同时测试 SideOfBill 的所有属性,因为它包含在 Bill 类中)。

这种情况常见吗?

【问题讨论】:

  • 如果它是一个实体,那么为什么不添加一个 ID 呢?
  • @OfirWinegarten 因为我不需要 ID。就是这样 - 我不确定是否应该将其建模为实体或值对象。我需要有更改状态的选项(在票据上绘制),但同时两张不在钱包中且具有相同绘制的票据可以被视为相等。
  • 那么我认为它应该是一个值对象,因为您不关心实例,而是关心内容。
  • 还有SideOfBill
  • @OfirWinegarten 可以这样。这种方法的问题是,如果我需要在侧面画一些东西——我必须每次都创建新的 SideOfTheBill,然后用这个新的更新的 SideOfTheBill 创建新的 Bill(因为值对象应该是不可变的)——看起来像很多代码?

标签: java entity domain-driven-design identity value-objects


【解决方案1】:

虽然这不是常见的做法,但值对象 (VO) 肯定可以是可变的(例如,出于性能原因)。但是,您需要确保不共享可变的 VOs

不过,对可变 VO 的需求可能是一个强有力的指标,表明您尝试建模的概念实际上是一个实体。问问自己的一个好问题是您是否对此实例的生命周期感兴趣。

例如,在您的情况下,保留对帐单所做更改的历史记录是否重要?如果是,那么应该将账单建模为一个实体。

如果我将此视为全球唯一的实体,我将不得不 Bill 的 id 实际上是由 [Wallet.Id & Bill.billType]。但在这种情况下,Wallet.id 并不自然适合 Bill 类。

不要忘记,从领域模型的角度来看,实体只能在其聚合根 (AR) 中唯一标识。这意味着billType 可以作为钱包中的账单 ID。

另请注意,如果需要,账单可以具有从数据库角度看的代理身份或(walletId,billType)复合 ID。

【讨论】:

  • 在这种情况下,Bill 在钱包中是唯一的(你不能有 2 个 BillType 相同的钞票)。我对法案的生命周期很感兴趣——这意味着我关心在法案的任何一侧绘制的图纸。我会制作这个实体,但我无法想象只有 BillType 作为 id 的 equals 方法。这意味着,如果我比较钱包中的账单和我手动创建的具有相同 BillType(但绘图不同)的账单,它将是相同的,这是没有意义的。
  • @bojanv55 这确实有道理。实体的状态无关紧要。这将是两个不同州的同一个实体。
  • 好的。似乎有点合乎逻辑:)
  • @bojanv55 所有实体都一样。我可以在内存中创建具有相同 ID 的任何聚合根的 10 个实例。您的工作单元应该能够处理这个问题(例如,通过使用身份映射)。如果您尝试添加具有相同 ID 的两个不同实例,则会引发异常,指出 ID 必须是唯一的或类似的东西。
【解决方案2】:

几个笔记:

  1. 重要的是要正确决定哪些对象是实体,哪些是 VO。 VO 身份由其内容(它所代表的值)决定。
    从您描述的情况来看,这不是很清楚。
    通常,10$ 是 10$,Money 是所有书籍在谈论 VO 时显示的第一个示例。
    但是,bill 可能不同并且可能被视为实体。我可以有一张 10 美元的钞票,你也可以有一张。它们的值是相同的(在那个术语中它们是相等的),但它们仍然是两个不同的实体,因此应该有一些身份字段。但是,这可能不是您的情况。
    如果您确实认为账单是相同的,如果它们具有相同的 billTypedrawing,那么它就是 VO。

  2. 对于讨论的第二部分,VO 通常在性质和定义上是不可变的。如果你有一个复杂的 VO,那么你可以考虑使用著名的 Builder 模式。

【讨论】:

    【解决方案3】:

    大体思路是使用以下规则:

    1) DDD 是关于业务和概念/概念之间的关系。实现没有严格定义。因此,最好先写详细的故事,然后在领域地图上展示您如何看待它。

    2) 一个实体是:

    许多对象从根本上不是由它们的属性定义的,而是 而是通过一条连续性和同一性的线索。 (埃文斯)

    例如,订单可以具有 OrderItems、Price、ShippingAddress 和其他属性。如果有人更改了 ShippingAddress,则订单将保持由其 OrderNumber 标识的相同对象,这不是新订单

    即使您在系统中有两个具有所有相同属性(OrderItems、Price、ShippingAddress)的订单,它们仍然是不同的实体。唯一的区别是身份:OrderNumber。

    3) 值对象由其所有属性定义。因此,将其作为不可变来执行是常见且方便的。

    明显的例子是价格:

    Price{
      readonly Currency Currency;
      readonly Decimal Amount;
    }
    value1 = new Price(Currency.USD, 1);
    value2 = new Price(Currency.USD, 1);
    
    Assert.IsTrue(value1 == value2);
    

    不太明显的例子是聚合根中 VO 的用法,这似乎适用于您的示例。

    Order 有 OrderItems,其中

    OrderItem{
      string ProductSKU;
      int Amount;
    }
    

    OrderItem设为Entity并添加OrderItemId属性可能会很方便,例如用于数据库编辑。从业务角度来看,大多数时候根本不知道OrderItemId。订单项目位于其聚合根 Order 内,并在外部严格标识为一对 {Order, OrderItem}。在这种情况下,您甚至无法在不首先访问其聚合根的情况下触摸 OrderItem。

    现在,如果我们查看 OrderItem,它绝对由其属性标识,那么它就是值对象。


    那么,“实体可以通过它的所有属性来识别吗?” - 不,这是值对象的概念。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-04-29
      • 1970-01-01
      • 1970-01-01
      • 2011-11-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多