首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >哪里是验证的最佳地点..。构造函数还是留给客户端调用?

哪里是验证的最佳地点..。构造函数还是留给客户端调用?
EN

Stack Overflow用户
提问于 2013-02-22 20:39:56
回答 3查看 225关注 0票数 2

这确实是一个一般性的问题(可能也是一个更主观的问题)。我有一些类,在这些类中,我使用接口来定义验证对象状态的标准方法。当我这么做的时候,我得挠我的头.最好是1.)允许构造函数(或初始化方法)自动筛选出错误信息或.2.)但是,允许客户端实例化对象,并让客户端在继续之前调用接口的IsValid属性或Validate()方法?

基本上,有一种方法是沉默的,但可能会产生误导,因为客户端可能不知道某些信息由于不符合验证标准而被过滤掉。另一种方法则会更直截了当,但也会增加一两步?这里典型的是什么?

好吧,经过一整天的努力,我终于想出了一个例子。请为我,因为它是不理想的,也绝不是什么美妙的东西,但希望服务足够好,以了解这一点。我现在的项目太复杂了,所以我编造了一些.相信我..。完全是虚构的。

好的,这个例子中的对象是:

客户端:表示客户端代码的(控制台应用程序btw)

IValidationInfo: --这是我在当前项目中使用的实际接口。它允许我为“后端”对象创建一个验证框架,因为业务逻辑可能已经足够复杂,所以客户端不一定要使用这些对象。这也允许我分离验证代码,并根据业务逻辑的需要进行调用。

OrderManager: --这是客户端代码可以用来管理订单的对象。这是对客户友好的谈话。

OrderSpecification: --这是客户端代码可以用来请求订单的对象。但是,如果业务逻辑不起作用,则可以引发异常(如果有必要,则不添加顺序,忽略异常.)在我的真实世界的例子中,我实际上有一个物体,它不像栅栏的哪一边那么黑白。因此,当我意识到我可以将验证请求(调用IsValid或IsValid())推到cilent上时,我的原始问题就出现了。

CustomerDescription:代表我分类过的客户(假装是从数据库中读取的)。

产品:代表也被分类的特定产品。

OrderDescription:代表官方订单request.The业务规则是客户不能订购任何他们没有被分类的东西(我知道.这不是很真实的世界,但它给了我一些工作.)

好的..。我刚意识到我不能在这里附加一个文件,这是代码。我为这次长时间的露面而道歉。这是使用我的验证接口创建客户端友好的前端和业务逻辑后端所能做的最好的事情:

公共类客户机{静态OrderManager orderMgr =新的OrderManager();

代码语言:javascript
复制
    static void Main(string[] args)
    {
        //Request a new order
        //Note:  Only the OrderManager and OrderSpecification are used by the Client as to keep the 
        //       Client from having to know and understand the framework beyond that point.
        OrderSpecification orderSpec = new OrderSpecification("Customer1", new Product(IndustryCategory.FoodServices, "Vending Items"));
        orderMgr.SubmitOrderRequest(orderSpec);
        Console.WriteLine("The OrderManager has {0} items for {1} customers.", orderMgr.ProductCount, orderMgr.CustomerCount);

        //Now add a second item proving that the business logic to add for an existing customer works
        Console.WriteLine("Adding another valid item for the same customer.");
        orderSpec = new OrderSpecification("Customer1", new Product(IndustryCategory.FoodServices, "Sodas"));
        orderMgr.SubmitOrderRequest(orderSpec);
        Console.WriteLine("The OrderManager now has {0} items for {1} customers.", orderMgr.ProductCount, orderMgr.CustomerCount);

        Console.WriteLine("Adding a new valid order for a new customer.");
        orderSpec = new OrderSpecification("Customer2", new Product(IndustryCategory.Residential, "Magazines"));
        orderMgr.SubmitOrderRequest(orderSpec);
        Console.WriteLine("The OrderManager now has {0} items for {1} customers.", orderMgr.ProductCount, orderMgr.CustomerCount);


        Console.WriteLine("Adding a invalid one will not work because the customer is not set up to receive these kinds of items.  Should get an exception with message...");
        try
        {
            orderSpec = new OrderSpecification("Customer3", new Product(IndustryCategory.Residential, "Magazines"));
            orderMgr.SubmitOrderRequest(orderSpec);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }

        Console.ReadLine();
    }
}

public interface IValidationInfo
{
    string[] ValidationItems { get; }

    bool IsValid { get; }

    void Validate();

    List<string> GetValidationErrors();

    string GetValidationError(string itemName);
}

public class OrderManager
{
    private List<OrderDescription> _orders = new List<OrderDescription>();
    public List<OrderDescription> Orders
    {
        get { return new List<OrderDescription>(_orders); }
        private set { _orders = value; }
    }

    public int ProductCount
    {
        get
        {
            int itemCount = 0;
            this.Orders.ForEach(o => itemCount += o.Products.Count);
            return itemCount;
        }
    }

    public int CustomerCount
    {
        get
        {
            //since there's only one customer per order, just return the number of orders
            return this.Orders.Count;
        }
    }

    public void SubmitOrderRequest(OrderSpecification orderSpec)
    {
        if (orderSpec.IsValid)
        {
            List<OrderDescription> orders = this.Orders;

            //Since the particular customer may already have an order, we might as well add to an existing
            OrderDescription existingOrder = orders.FirstOrDefault(o => string.Compare(orderSpec.Order.Customer.Name, o.Customer.Name, true) == 0) as OrderDescription;
            if (existingOrder != null)
            {
                List<Product> existingProducts = orderSpec.Order.Products;
                orderSpec.Order.Products.ForEach(p => existingOrder.AddProduct(p));
            }
            else
            {
                orders.Add(orderSpec.Order);
            }
            this.Orders = orders;
        }
        else
            orderSpec.Validate(); //Let the OrderSpecification pass the business logic validation down the chain
    }
}


public enum IndustryCategory
{
    Residential,
    Textile,
    FoodServices,
    Something
}


public class OrderSpecification : IValidationInfo
{
    public OrderDescription Order { get; private set; }


    public OrderSpecification(string customerName, Product product)
    {
        //Should use a method in the class to search and retrieve Customer... pretending here
        CustomerDescription customer = null;
        switch (customerName)
        {
            case "Customer1":
                customer = new CustomerDescription() { Name = customerName, Category = IndustryCategory.FoodServices };
                break;
            case "Customer2":
                customer = new CustomerDescription() { Name = customerName, Category = IndustryCategory.Residential };
                break;
            case "Customer3":
                customer = new CustomerDescription() { Name = customerName, Category = IndustryCategory.Textile };
                break;
        }


        //Create an OrderDescription to potentially represent the order... valid or not since this is
        //a specification being used to request the order
        this.Order = new OrderDescription(new List<Product>() { product }, customer);

    }

    #region IValidationInfo Members
    private readonly string[] _validationItems =
    {
        "OrderDescription"
    };
    public string[] ValidationItems
    {
        get { return _validationItems; }
    }

    public bool IsValid
    {
        get
        {
            List<string> validationErrors = GetValidationErrors();
            if (validationErrors != null && validationErrors.Count > 0)
                return false;
            else
                return true;
        }
    }

    public void Validate()
    {
        List<string> errorMessages = GetValidationErrors();
        if (errorMessages != null && errorMessages.Count > 0)
        {
            StringBuilder errorMessageReported = new StringBuilder();
            errorMessages.ForEach(em => errorMessageReported.AppendLine(em));
            throw new Exception(errorMessageReported.ToString());
        }
    }

    public List<string> GetValidationErrors()
    {
        List<string> errorMessages = new List<string>();
        foreach (string item in this.ValidationItems)
        {
            string errorMessage = GetValidationError(item);
            if (!string.IsNullOrEmpty(errorMessage))
                errorMessages.Add(errorMessage);
        }

        return errorMessages;
    }

    public string GetValidationError(string itemName)
    {
        switch (itemName)
        {
            case "OrderDescription":
                return ValidateOrderDescription();
            default:
                return "Invalid item name.";
        }
    }

    #endregion

    private string ValidateOrderDescription()
    {
        string errorMessage = string.Empty;

        if (this.Order == null)
            errorMessage = "Order was not instantiated.";
        else
        {
            if (!this.Order.IsValid)
            {
                List<string> orderErrors = this.Order.GetValidationErrors();
                orderErrors.ForEach(ce => errorMessage += "\n" + ce);
            }
        }

        return errorMessage;
    }

}

public class CustomerDescription : IValidationInfo
{
    public string Name { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public int ZipCode { get; set; }
    public IndustryCategory Category { get; set; }

    #region IValidationInfo Members
    private readonly string[] _validationItems =
    {
        "Name",
        "Street",
        "City",
        "State",
        "ZipCode",
        "Category"
    };
    public string[] ValidationItems
    {
        get { return _validationItems; }
    }

    public bool IsValid
    {
        get
        {
            List<string> validationErrors = GetValidationErrors();
            if (validationErrors != null && validationErrors.Count > 0)
                return false;
            else
                return true;
        }
    }

    public void Validate()
    {
        List<string> errorMessages = GetValidationErrors();
        if (errorMessages != null && errorMessages.Count > 0)
        {
            StringBuilder errorMessageReported = new StringBuilder();
            errorMessages.ForEach(em => errorMessageReported.AppendLine(em));
            throw new Exception(errorMessageReported.ToString());
        }
    }

    public List<string> GetValidationErrors()
    {
        List<string> errorMessages = new List<string>();
        foreach (string item in this.ValidationItems)
        {
            string errorMessage = GetValidationError(item);
            if (!string.IsNullOrEmpty(errorMessage))
                errorMessages.Add(errorMessage);
        }

        return errorMessages;
    }

    public string GetValidationError(string itemName)
    {
        //Validation methods should be called here... pretending nothings wrong for sake of discussion & simplicity
        switch (itemName)
        {
            case "Name":
                return string.Empty;
            case "Street":
                return string.Empty;
            case "City":
                return string.Empty;
            case "State":
                return string.Empty;
            case "ZipCode":
                return string.Empty;
            case "Category":
                return string.Empty;
            default:
                return "Invalid item name.";
        }
    }

    #endregion
}


public class Product
{
    public IndustryCategory Category { get; private set; }
    public string Description { get; private set; }

    public Product(IndustryCategory category, string description)
    {
        this.Category = category;
        this.Description = description;
    }
}







public class OrderDescription : IValidationInfo
{
    public CustomerDescription Customer { get; private set; }

    private List<Product> _products = new List<Product>();
    public List<Product> Products
    {
        get { return new List<Product>(_products); }
        private set { _products = value; }
    }

    public OrderDescription(List<Product> products, CustomerDescription customer)
    {
        this.Products = products;
        this.Customer = customer;
    }

    public void PlaceOrder()
    {
        //If order valid, place
        if (this.IsValid)
        {
            //Do stuff to place order
        }
        else
            Validate(); //cause the exceptions to be raised with the validate because business rules were broken
    }

    public void AddProduct(Product product)
    {
        List<Product> productsToEvaluate = this.Products;
        //some special read, validation, quantity check, pre-existing, etc here
        // doing other stuff... 
        productsToEvaluate.Add(product);
        this.Products = productsToEvaluate;
    }

    #region IValidationInfo Members

    private readonly string[] _validationItems =
    {
        "Customer",
        "Products"
    };
    public string[] ValidationItems
    {
        get { return _validationItems; }
    }

    public bool IsValid
    {
        get
        {
            List<string> validationErrors = GetValidationErrors();
            if (validationErrors != null && validationErrors.Count > 0)
                return false;
            else
                return true;
        }
    }

    public void Validate()
    {
        List<string> errorMessages = GetValidationErrors();
        if (errorMessages != null && errorMessages.Count > 0)
        {
            StringBuilder errorMessageReported = new StringBuilder();
            errorMessages.ForEach(em => errorMessageReported.AppendLine(em));
            throw new Exception(errorMessageReported.ToString());
        }
    }

    public List<string> GetValidationErrors()
    {
        List<string> errorMessages = new List<string>();
        foreach (string item in this.ValidationItems)
        {
            string errorMessage = GetValidationError(item);
            if (!string.IsNullOrEmpty(errorMessage))
                errorMessages.Add(errorMessage);
        }

        return errorMessages;
    }

    public string GetValidationError(string itemName)
    {
        switch (itemName)
        {
            case "Customer":
                return ValidateCustomer();
            case "Products":
                return ValidateProducts();
            default:
                return "Invalid item name.";
        }
    }

    #endregion

    #region Validation Methods

    private string ValidateCustomer()
    {
        string errorMessage = string.Empty;

        if (this.Customer == null)
            errorMessage = "CustomerDescription is missing a valid value.";
        else
        {
            if (!this.Customer.IsValid)
            {
                List<string> customerErrors = this.Customer.GetValidationErrors();
                customerErrors.ForEach(ce => errorMessage += "\n" + ce);
            }
        }

        return errorMessage;
    }

    private string ValidateProducts()
    {
        string errorMessage = string.Empty;

        if (this.Products == null || this.Products.Count <= 0)
            errorMessage = "Invalid Order. Missing Products.";
        else
        {
            foreach (Product product in this.Products)
            {
                if (product.Category != Customer.Category)
                {
                    errorMessage += string.Format("\nThe Product, {0}, category does not match the required Customer category for {1}", product.Description, Customer.Name);
                }
            }
        }
        return errorMessage;
    }
    #endregion
}
EN

回答 3

Stack Overflow用户

发布于 2013-02-22 20:42:44

如果信息有效,您有什么理由不希望构造函数大声抛出异常呢?根据我的经验,最好避免在无效状态下创建对象。

票数 3
EN

Stack Overflow用户

发布于 2013-02-22 20:55:49

如果我正确理解的话,基本上有两个问题--你是否应该马上就失败,或者你是否应该忽略/假定某些信息。

1)我总是喜欢尽快失败--好的例子是在编译时失败,而在运行时失败--您总是希望在编译时失败。所以,如果某个物体的状态出了问题,就像Jon说的--尽可能大声地抛出异常,并处理它--不要在路上引入额外的复杂性,因为你要去的是if /往昔。

2)当涉及到用户输入时,如果您能够简单地自动过滤错误--只需这样做。例如,我几乎从不向用户询问国家--如果我真的需要它,我会自动从IP中检测到它,并在表单中显示它。如果用户只需要确认/更改数据就更容易了--而且我不需要处理空情况。

现在,如果我们讨论的是代码在某些处理过程中生成的数据--对我来说,情况完全不同--我总是想知道尽可能多(以便更容易地调试),而且理想情况下,您永远不应该销毁任何信息。

最后,在您的例子中,我建议您将IsValid保持为简单的yes/no (不是是/否/可能/kindaok/等等)。如果您可以自动修复一些问题--请执行它,但请考虑它们在IsValid中保留了对象-是的。对于其他所有内容,您将抛出异常并转到IsValid=no。

票数 0
EN

Stack Overflow用户

发布于 2013-02-22 21:01:44

完全取决于客户。就像你刚才提到的那样。默认情况下,1号方法是我最喜欢的方法。创建具有良好封装性的智能类并向客户端隐藏详细信息。聪明程度取决于谁将使用该对象。如果客户是业务意识,你可以透露的细节,根据这一意识的水平。这是一个二分法,不应被视为黑色或白色。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/15032808

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档