以产品实体或聚合为例,该实体或聚合引用另一个名为类别的实体。在发生 CategorizeProduct 等域事件之前,类别的状态为空。要在C#中表示,我必须使用NULL
。我不能在C#中使用Nullable<T>
,因为它只能用于值类型。
在DDD中使用NULL
是反模式吗?已经提出的一些解决方案是(1)使用MayBe<T>
(2)在单个实体的位置使用collection。
MayBe<t>
是一个选项,但有没有不同的方法来处理DDD中的空实体状态?
答案 0 :(得分:3)
对于这些类型的事物,您可以使用null object pattern。想想例如String.Empty
属性就是一个简单的例子。
如果你的null
对象的某些方法应该有不同的行为,你可以使它成为一个派生自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
属性的值,因此不需要子类,我们可以简单地使用了静态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 /属性与其他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;
}
}
答案 1 :(得分:2)
我理解您的担忧 - 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; }
}