在为您的领域实体建模时,最好考虑现实世界的影响。假设您正在处理一个Employee
实体。
员工需要姓名
我们知道,在现实世界中,员工必须始终拥有姓名。员工不可能没有名字。换句话说,一个人不能在不指定员工姓名的情况下“构建”员工。所以,使用参数化的构造函数!我们也知道员工姓名不能更改 - 因此我们通过创建私有设置器来防止这种情况发生。使用 .NET 类型系统来验证您的员工是一种非常强大的验证形式。
public string Name { get; private set; }
public Employee(string name)
{
Name = name;
}
有效名称有一些规则
现在它开始变得有趣了。名字有一定的规则。让我们采取简单的路线并假设有效名称是不为空或不为空的名称。在上面的代码示例中,未验证以下业务规则。至此,我们目前仍然可以创建无效员工!让我们通过修改我们的设置器来防止这种情况发生:
public string Name
{
get
{
return name;
}
private set
{
if (String.IsNullOrWhiteSpace(value))
{
throw new ArgumentOutOfRangeException("value", "Employee name cannot be an empty value");
}
name = value;
}
}
就个人而言,我更喜欢在私有 setter 中而不是在构造函数中使用此逻辑。二传手并非完全不可见。实体本身仍然可以更改它,我们需要确保其有效性。另外,总是抛出异常!
暴露某种形式的IsValid()
方法怎么样?
以上述Employee
实体为例。方法在哪里以及如何IsValid()
工作?
你会允许创建一个无效的员工,然后期望开发人员通过检查来检查它的有效性IsValid()
吗?这是一个薄弱的设计 - 在您知道之前,无名员工将在您的系统周围游荡,造成严重破坏。
但也许您想公开名称验证逻辑?
我们不想捕获控制流的异常。灾难性系统故障除外。我们也不想在我们的代码库中重复这些验证规则。所以,也许暴露这个验证逻辑并不是一个坏主意(但仍然不是最好的!)。
你可以做的是提供一个静态IsValidName(string)
方法:
public static bool IsValidName(string name)
{
return (String.IsNullOrWhiteSpace(value))
}
我们的属性现在会有所改变:
public string Name
{
get
{
return name;
}
private set
{
if (!Employee.IsValidName(value))
{
throw new ArgumentOutOfRangeException("value", "Employee name cannot be an empty value");
}
name = value;
}
}
但是这个设计有一些可疑之处......
我们现在开始为实体的各个属性生成验证方法。如果一个属性附加了各种规则和行为,也许这表明我们可以为它创建一个值对象!
public PersonName : IEquatable<PersonName>
{
public string Name
{
get
{
return name;
}
private set
{
if (!PersonName.IsValid(value))
{
throw new ArgumentOutOfRangeException("value", "Person name cannot be an empty value");
}
name = value;
}
}
private PersonName(string name)
{
Name = name;
}
public static PersonName From(string name)
{
return new PersonName(name);
}
public static bool IsValid(string name)
{
return !String.IsNullOrWhiteSpace(value);
}
// Don't forget to override .Equals
}
现在我们的Employee
实体可以简化(我已经排除了空引用检查):
public Employee
{
public PersonName Name { get; private set; }
public Employee(PersonName name)
{
Name = name;
}
}
我们的客户端代码现在看起来像这样:
if(PersonName.IsValid(name))
{
employee = new Employee(PersonName.From(name));
}
else
{
// Send a validation message to the user or something
}
那么我们在这里做了什么?
我们确保我们的领域模型始终保持一致。极其重要。无法创建无效实体。此外,我们使用值对象来提供进一步的“丰富性”。 PersonName
给了客户端代码更多的控制和更多的权力,也简化了Employee
。