3

假设一个应用程序有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方法。

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

4

3 回答 3

3

我认为更好的问题是外部课程是否应该能够EmployerId直接修改。

大多数情况下,创建突变的方法应该暴露为动词,诸如此类ChangeEmployer(Employer newEmployer)。这样做会使其更加明确,并允许您更轻松地引发领域事件。在这种情况下,该set方法将是private只有拥有的类才能调用它。

也就是说,任何更改都EmployerId应该在 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; }
     } 
}
于 2013-07-11T18:18:42.920 回答
2

您的方法的缺点之一是无法返回有关业务规则失败原因的“错误”信息。在您的示例中,您将输出定向到Console,这对调用代码没有好处。

我建议构建一组可以应用于您的业务对象的业务规则类,然后根据事件进行处理(即表示层调用Validate业务对象),然后List<string>可以返回一个集合(即错误)并由调用代码(即表示层)。

于 2013-07-11T18:23:45.663 回答
2

我喜欢 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 的解决方案之间的中间值。

于 2013-07-11T18:49:46.007 回答