实体可以通过其所有属性来识别吗?
Can Entity be identified by all of its properties?
问题是 - 实体可以由其所有属性定义还是仅由其 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 class。
最自然的事情是我将 Bill 视为实体并使用 equals 方法来测试所有 Bill 属性(同时测试 SideOfBill 的所有属性,因为它包含在 Bill class 中)。
这种常见情况有吗?
虽然这不是常见的做法,但值对象 (VO) 肯定可以是可变的(例如出于性能原因)。但是,您需要确保不共享可变 VO。
不过,对可变 VO 的需求或许是一个强有力的指标,表明您尝试建模的概念实际上是一个实体。问自己一个很好的问题是您是否对此实例的生命周期感兴趣。
例如,在您的情况下,保留对帐单所做更改的历史记录是否重要?如果是,则应将账单建模为实体。
If I treat this as the entity that is globally unique, I would have to
have id for Bill that is actually composite from [Wallet.Id &
Bill.billType]. But Wallet.id does not fits naturally to Bill class in this case.
不要忘记,从领域模型的角度来看,实体只能在其聚合根 (AR) 中唯一标识。这意味着 billType
可以作为钱包内的账单 ID。
另请注意,如果需要,帐单可以具有数据库角度的代理身份或(walletId、billType)复合 ID。
一些注意事项:
正确决定哪些对象是实体,哪些是 VO 很重要。 VO的身份是由它的内容决定的(它代表的价值)。
从你描述的情况看不太清楚。
一般10$就是10$,Money
是所有书上讲VO的第一个例子
但是,bill 可能不同并且可能被视为实体。我可以有一张 10 美元的钞票,你也可以有一张。它们的值相同(在该术语中它们是相等的),但它们仍然是两个不同的实体,因此应该有一些身份字段。然而,这可能不是你的情况。
如果你做认为账单是一样的,如果他们有相同的billType
和drawing
那么它就是一个VO。
对于讨论的第二部分,VO 通常在本质上和定义上都是不可变的。如果你有一个复杂的 VO 那么你可以考虑使用著名的 Builder 模式。
大体思路是使用下一条规则:
1) DDD 是关于 concepts/notions 之间的业务和关系的。实现没有严格定义。因此,最好写详细的故事,然后在领域地图上展示你的看法。
2) 一个实体是:
Many objects are not fundamentally defined by their attributes, but
rather by a thread of continuity and identity. (Evans)
例如,订单可以具有 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 的用法,这似乎适用于您的例子。
订单有 OrderItems,其中
OrderItem{
string ProductSKU;
int Amount;
}
将OrderItem
作为实体并添加OrderItemId
属性可能会很方便,例如,用于数据库编辑。从商业角度来看,大多数时候根本没有 OrderItemId
的概念。订单项目位于其聚合根 Order
内,并在外部严格标识为一对 {Order, OrderItem}
。在这种情况下,您甚至无法在不首先访问其聚合根的情况下触摸 OrderItem。
现在,如果我们查看 OrderItem,它完全由其属性标识,那么它就是值对象。
所以,"can Entity be identified by all of its properties?" - 不,这是值对象的概念。
问题是 - 实体可以由其所有属性定义还是仅由其 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 class。
最自然的事情是我将 Bill 视为实体并使用 equals 方法来测试所有 Bill 属性(同时测试 SideOfBill 的所有属性,因为它包含在 Bill class 中)。
这种常见情况有吗?
虽然这不是常见的做法,但值对象 (VO) 肯定可以是可变的(例如出于性能原因)。但是,您需要确保不共享可变 VO。
不过,对可变 VO 的需求或许是一个强有力的指标,表明您尝试建模的概念实际上是一个实体。问自己一个很好的问题是您是否对此实例的生命周期感兴趣。
例如,在您的情况下,保留对帐单所做更改的历史记录是否重要?如果是,则应将账单建模为实体。
If I treat this as the entity that is globally unique, I would have to have id for Bill that is actually composite from [Wallet.Id & Bill.billType]. But Wallet.id does not fits naturally to Bill class in this case.
不要忘记,从领域模型的角度来看,实体只能在其聚合根 (AR) 中唯一标识。这意味着 billType
可以作为钱包内的账单 ID。
另请注意,如果需要,帐单可以具有数据库角度的代理身份或(walletId、billType)复合 ID。
一些注意事项:
正确决定哪些对象是实体,哪些是 VO 很重要。 VO的身份是由它的内容决定的(它代表的价值)。
从你描述的情况看不太清楚。
一般10$就是10$,Money
是所有书上讲VO的第一个例子
但是,bill 可能不同并且可能被视为实体。我可以有一张 10 美元的钞票,你也可以有一张。它们的值相同(在该术语中它们是相等的),但它们仍然是两个不同的实体,因此应该有一些身份字段。然而,这可能不是你的情况。
如果你做认为账单是一样的,如果他们有相同的billType
和drawing
那么它就是一个VO。对于讨论的第二部分,VO 通常在本质上和定义上都是不可变的。如果你有一个复杂的 VO 那么你可以考虑使用著名的 Builder 模式。
大体思路是使用下一条规则:
1) DDD 是关于 concepts/notions 之间的业务和关系的。实现没有严格定义。因此,最好写详细的故事,然后在领域地图上展示你的看法。
2) 一个实体是:
Many objects are not fundamentally defined by their attributes, but rather by a thread of continuity and identity. (Evans)
例如,订单可以具有 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 的用法,这似乎适用于您的例子。
订单有 OrderItems,其中
OrderItem{
string ProductSKU;
int Amount;
}
将OrderItem
作为实体并添加OrderItemId
属性可能会很方便,例如,用于数据库编辑。从商业角度来看,大多数时候根本没有 OrderItemId
的概念。订单项目位于其聚合根 Order
内,并在外部严格标识为一对 {Order, OrderItem}
。在这种情况下,您甚至无法在不首先访问其聚合根的情况下触摸 OrderItem。
现在,如果我们查看 OrderItem,它完全由其属性标识,那么它就是值对象。
所以,"can Entity be identified by all of its properties?" - 不,这是值对象的概念。