我正在设计一个系统,它有一个简单的实体框架支持的域对象,其中包含我需要根据一系列规则更新的字段 - 我希望逐步实现这些规则(以敏捷方式)并且因为我使用EF我对将每个规则放入域对象持怀疑态度。但是,我想避免编写“程序代码”并使用贫血域模型。这一切都需要测试。
例如,对象是:
class Employee {
private string Name;
private float Salary;
private float PensionPot;
private bool _pension;
private bool _eligibleForPension;
}
我需要构建规则,例如“如果Salary高于100,000且_eligibleForPension为false,则将_eligibleForPension设置为true”和“if _pension为true,然后将_eligibleForPension设置为true”。
大约有20条这样的规则,我正在寻找建议,无论它们是应该在Employee类中实现还是在EmployeeRules类中实现?我的第一个想法是为从“规则”继承的每个规则创建一个单独的类,然后将每个规则应用于Employee类,可能使用Visitor模式但我必须将所有字段暴露给规则才能执行此操作感觉不对。虽然在Employee类上有每个规则也不太合适。如何实施?
第二个问题是实际的Employees是支持DB的实体框架实体,所以我不乐意为这些“实体”添加逻辑 - 特别是当我需要模拟对象来测试每个规则的单元时。如果他们有我在相同对象上测试的规则,我怎么能嘲笑他们?
我一直在考虑使用AutoMapper在应用规则之前转换为更简单的域对象,但之后需要自己管理字段的更新。对此也有任何建议吗?
答案 0 :(得分:7)
一种方法是使规则成为Employee
的内部类。这种方法的好处是字段可以保持私密。此外,可以通过Employee类本身强制执行规则,确保在需要时始终调用它们:
class Employee
{
string id;
string name;
float salary;
float pensionPot;
bool pension;
bool eligibleForPension;
public void ChangeSalary(float salary)
{
this.salary = salary;
ApplyRules();
}
public void MakeEligibleForPension()
{
this.eligibleForPension = true;
ApplyRules(); // may or may not be needed
}
void ApplyRules()
{
rules.ForEach(rule => rule.Apply(this));
}
readonly static List<IEmployeeRule> rules;
static Employee()
{
rules = new List<IEmployeeRule>
{
new SalaryBasedPensionEligibilityRule()
};
}
interface IEmployeeRule
{
void Apply(Employee employee);
}
class SalaryBasedPensionEligibilityRule : IEmployeeRule
{
public void Apply(Employee employee)
{
if (employee.salary > 100000 && !employee.eligibleForPension)
{
employee.MakeEligibleForPension();
}
}
}
}
这里的一个问题是Employee类必须包含所有规则实现。这不是一个主要问题,因为规则体现了与员工养老金相关的业务逻辑,因此它们确实属于一起。
答案 1 :(得分:4)
业务规则通常是一个有趣的话题。聚合/实体不变量和业务规则之间肯定存在差异。业务规则可能需要外部数据,我不同意更改聚合/实体的规则。
您应该考虑规则的规范模式。规则应该基本上只是返回它是否被破坏,可能有各种描述。
在eulerfx使用的示例SalaryBasedPensionEligibilityRule
中,可能需要一些PensionThreshold
。这条规则确实看起来更像是一项任务,因为规则实际上并没有检查实体的任何有效性。
所以我建议规则是决策机制,任务是改变状态。
据说你可能想在这里向实体征求意见,因为你可能不想公开这个州:
public class Employee
{
float salary;
bool eligibleForPension;
public bool QualifiesForPension(float pensionThreshold)
{
return salary > pensionThreshold && !eligibleForPension;
}
public void MakeEligibleForPension()
{
eligibleForPension = true;
}
}
这坚持命令/查询分离的想法。
如果你是直接从你的ORM对象构建而不想或不能包含所有行为那么那就没关系---但它肯定会有所帮助:)