在属性设置器中强制执行业务规则的正确方法

时间:2013-07-11 18:10:35

标签: c# properties

假设一个应用程序已经由用户创建了一个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方法。

这是生产环境中可接受的解决方案吗?有什么方法可以改进,或者当应用程序不是一个人为的例子时,这可能导致后来的任何陷阱? (除了用户不知道有效输入是什么的事实。)

3 个答案:

答案 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之间的中间点。