DDD 中的空实体
Null entities in DDD
以 Product 实体或聚合为例,它引用了另一个名为 Category 的实体。在像 CategorizeProduct 这样的领域事件发生之前,类别的状态是空的。为了在 C# 中表示,我必须使用 NULL
。我不能在 C# 中使用 Nullable<T>
,因为它只能用于值类型。
在 DDD 中使用 NULL
是反模式吗?建议的一些解决方案是 (1) 使用 MayBe<T>
(2) 使用 collection 代替单个实体。
MayBe<t>
是一个选项,但是在 DDD 中是否有不同的方法来处理空实体状态?
我理解您的担忧 - NULL 可能意味着尚未加载该类别,或者可能意味着尚未分配任何类别。
一个选项是添加一个可为 null 的 属性 CategoryId,其中包含分配的类别的 ID。如果分配了类别,则始终填写 ID,否则为空。这意味着如果填写了CategoryID而Category为null,则表示分配了一个类别但没有加载。
如果您使用 Entity Framework 加载或保留您的域,这种方法自然适合。
public class Product
{
public Product()
{
// validate entity
}
public int? CategoryId { get; private set; }
public Category Category { get; private set; }
}
对于这些类型的事情,您可以使用 null object pattern。想想例如String.Empty
属性 作为一个简单的例子。
如果您的 null
对象的某些方法应该有不同的行为,您可以将其设为私有内部 class,它派生自 Category
并覆盖特定行为。
为了你的Category
:
public class Category
{
private class UnassignedCategory : Category
{
public UnassignedCategory() : base("") { }
}
private static readonly UnassignedCategory _unassigned = new UnassignedCategory();
public static Category Unassigned
{
get { return _unassigned; }
}
public Category(string name)
{
this.Name = name;
}
public string Name { get; private set; }
public bool IsAssigned
{
get { return ReferenceEquals(this, _unassigned); }
}
}
请注意,在上面的示例中,由于 Category
上的默认无参数构造函数不存在,并且无法更改 Name
属性 的值,因此subclass 并不是真正需要的,我们可以简单地使用静态 Category
实例。
用法:
public class Product
{
public Product()
{
Category = Category.Unassigned;
// other stuff
}
// other stuff.
public void AssignCategory(Category category)
{
// Any associated logic.
Category = category;
}
public Category Category { get; private set; }
}
如果您要从数据库中保存和加载 null
(即未分配的)类别,您可能希望使用此主题的变体,为 [= 使用明确的(不可变的)id/attribute 43=] 与其他 Category
属性一起保存并用于 IsAssigned
检查。
可行的替代方案
一个完全可行的替代方案是继续做您现在正在做的事情,使用 null
参考。如果需要,您可以添加一些扩展方法,让您的生活更轻松。
public static MyCategoryExtensions
{
public static bool IsNullOrEmpty(this Category category)
{
return category == null || string.IsNullOrEmpty(category.Name);
}
public static Category EmptyIfNull(this Category category)
{
return category ?? new Category("");
}
public static Category DefaultIfNullOrEmpty(this Category category)
{
return category.IsNullOrEmpty()
? new Category(Category.DefaultName)
: category;
}
}
以 Product 实体或聚合为例,它引用了另一个名为 Category 的实体。在像 CategorizeProduct 这样的领域事件发生之前,类别的状态是空的。为了在 C# 中表示,我必须使用 NULL
。我不能在 C# 中使用 Nullable<T>
,因为它只能用于值类型。
在 DDD 中使用 NULL
是反模式吗?建议的一些解决方案是 (1) 使用 MayBe<T>
(2) 使用 collection 代替单个实体。
MayBe<t>
是一个选项,但是在 DDD 中是否有不同的方法来处理空实体状态?
我理解您的担忧 - NULL 可能意味着尚未加载该类别,或者可能意味着尚未分配任何类别。
一个选项是添加一个可为 null 的 属性 CategoryId,其中包含分配的类别的 ID。如果分配了类别,则始终填写 ID,否则为空。这意味着如果填写了CategoryID而Category为null,则表示分配了一个类别但没有加载。
如果您使用 Entity Framework 加载或保留您的域,这种方法自然适合。
public class Product
{
public Product()
{
// validate entity
}
public int? CategoryId { get; private set; }
public Category Category { get; private set; }
}
对于这些类型的事情,您可以使用 null object pattern。想想例如String.Empty
属性 作为一个简单的例子。
如果您的 null
对象的某些方法应该有不同的行为,您可以将其设为私有内部 class,它派生自 Category
并覆盖特定行为。
为了你的Category
:
public class Category
{
private class UnassignedCategory : Category
{
public UnassignedCategory() : base("") { }
}
private static readonly UnassignedCategory _unassigned = new UnassignedCategory();
public static Category Unassigned
{
get { return _unassigned; }
}
public Category(string name)
{
this.Name = name;
}
public string Name { get; private set; }
public bool IsAssigned
{
get { return ReferenceEquals(this, _unassigned); }
}
}
请注意,在上面的示例中,由于 Category
上的默认无参数构造函数不存在,并且无法更改 Name
属性 的值,因此subclass 并不是真正需要的,我们可以简单地使用静态 Category
实例。
用法:
public class Product
{
public Product()
{
Category = Category.Unassigned;
// other stuff
}
// other stuff.
public void AssignCategory(Category category)
{
// Any associated logic.
Category = category;
}
public Category Category { get; private set; }
}
如果您要从数据库中保存和加载 null
(即未分配的)类别,您可能希望使用此主题的变体,为 [= 使用明确的(不可变的)id/attribute 43=] 与其他 Category
属性一起保存并用于 IsAssigned
检查。
可行的替代方案
一个完全可行的替代方案是继续做您现在正在做的事情,使用 null
参考。如果需要,您可以添加一些扩展方法,让您的生活更轻松。
public static MyCategoryExtensions
{
public static bool IsNullOrEmpty(this Category category)
{
return category == null || string.IsNullOrEmpty(category.Name);
}
public static Category EmptyIfNull(this Category category)
{
return category ?? new Category("");
}
public static Category DefaultIfNullOrEmpty(this Category category)
{
return category.IsNullOrEmpty()
? new Category(Category.DefaultName)
: category;
}
}