假设一个应用程序已经由用户创建了一个Employer
对象,程序正在提示用户输入名为EmployerId
的属性。在此示例中,应用程序不会让用户输入无效的EmployeeId。我已经嘲笑了以下解决方案来解决问题。
public int EmployerId
{
get { return employerId; }
set
{
EmployerId = SetEmployerId();
}
}
public int SetEmployerId()
{
int id = 0;
Console.Write("Enter employer ID: ");
id = Convert.ToInt32(Console.ReadLine());
while (id < 5 || id > 10) //silly logic for illustrative purposes
{
Console.Write("Invalid id, enter another id: ");
id = Convert.ToInt32(Console.ReadLine());
}
return this.employerId = id;
}
这可以防止任何繁重的事情从属性的设置者中解脱出来,并将该责任委托给SetEmployerId
方法。
这是生产环境中可接受的解决方案吗?有什么方法可以改进,或者当应用程序不是一个人为的例子时,这可能导致后来的任何陷阱? (除了用户不知道有效输入是什么的事实。)
答案 0 :(得分:3)
我认为更好的问题是外部课程是否应该能够直接修改EmployerId
。
创建突变的方法通常会以动词形式出现,如ChangeEmployer(Employer newEmployer)
等等。这样做可以使它更明确,并允许您更轻松地提出域事件。在这种情况下,set
方法将为private
,以便只有拥有的类才能调用它。
也就是说,EmployerId
的任何更改都应该在setter中验证,逻辑只在一个地方,而不是散布在多个方法中。
Karl Anderson的回答非常明确,将业务逻辑放在这样的setter中会阻止返回非异常错误。这是事实,在使用属性设置器之前应该考虑到这一点。
他也对验证对象提出了一个很好的观点,你的聚合实体可能只能通过Id引用彼此,因为它有一个单独的业务验证对象来验证那些ID可能是一个很好的选择。您可以在多个地方重复使用验证器,但在一天结束时,唯一重要的地方是实体内部,因为这是唯一必须始终一致的地方。
public class Employee
{
private EmployerId employerId;
public Employee(EmployerId id /* other params such as name etc */)
{
var employerSetResult = this.SetEmployerId(id);
if(!result.Success)
throw new ArgumentException("id", "id is invalid");
}
// this is a separate method because you will need to set employerId
// from multiple locations and should only ever call SetEmployerId
// internally
public Result ChangeEmployer(EmployerId id)
{
var result = this.SetEmployerId(id);
if(result.Success)
DomainEventPublisher.Publish(
new EmployeeEmployerChanged(id, this.id));
return result;
}
private Result SetEmployerId(Employer id)
{
var result = EmployerIdValidator.Validate(id);
if(result.Success)
this.employerId = id;
return result;
}
}
public static class EmployerIdValidator
{
public static Result Validate(EmployerId id)
{
if(id < 5)
return new Result(success: false, new ValidationResult("Id too low"));
else if (id > 10)
return new Result(success: false, new ValidationResult("Id too high"));
return new Result(success:true);
}
}
public class Result
{
public bool Success {get {return this.success;}}
public IEnumerable<ValidationResult> ValidationResults
{
get{ return this.validationResults; }
}
}
答案 1 :(得分:2)
您的方法的一个缺点是无法返回有关业务规则失败原因的“错误”信息。在您的示例中,您将输出定向到Console
,这使得调用代码无效。
我建议构建一组业务规则类,这些类可以应用于业务对象,然后在事件上处理(即表示层在业务对象上调用Validate
),然后是集合(即{错误的{1}}可以由调用代码(即表示层)返回和使用。
答案 2 :(得分:1)
我喜欢Karl和Mgetz谈到的方法。 只是想要,如果你想使用setter和getter,并将你的业务逻辑与演示文稿分开,你注定要使用Exceptions,你的代码就像这样:
public class WrongEmployeeIDException : Exception
{
public WrongEmployeeIDException(string message) : base(message) { }
}
public class Employee
{
private int employerId;
public int EmployerId
{
get { return employerId; }
set
{
if (value < 5 || value > 10)
{
throw new WrongEmployeeIDException("Invalid id");
}
employerId = value;
}
}
}
public void Main()
{
int id;
string input;
bool isSetted = false;
Employee employee = new Employee();
while (!isSetted)
{
Console.Write("Enter employer ID: ");
try
{
input = Console.ReadLine();
id = Convert.ToInt32(input);
employee.EmployerId = id;
isSetted = true;
}
catch (WrongEmployeeIDException ex)
{
Console.WriteLine(ex.Message);
//not satisfied to bussiness rules
}
catch (FormatException ex)
{
Console.WriteLine(ex.Message);
//Convert.ToInt32 thrown exception
}
catch
{
//something more bad happend
}
}
}
但不建议使用,因为使用Magetz的解决方案,验证逻辑将更顺畅,更快速地执行。这是你的思绪和Magetz之间的中间点。