2

これは本当に一般的な(そしておそらくもっと主観的な)質問です。オブジェクトの状態を検証するための標準的なアプローチを定義するためにインターフェイスを使用するクラスがいくつかあります。これを行ったとき、頭をかきむしりました... 1.)コンストラクター(または初期化メソッド)が誤った情報を自動的にサイレントにフィルターで除外できるようにするか... 2。)クライアントがインスタンス化できるようにするのが最善ですただし、オブジェクトを作成し、先に進む前に、クライアントがインターフェイスのIsValidプロパティまたはValidate()メソッドを呼び出せるようにしますか?

基本的に1つのアプローチは無言ですが、検証基準を満たしていないために特定の情報が除外されたことにクライアントが気付かない可能性があるという点で誤解を招く可能性があります。他のアプローチはもっと簡単ですが、1、2ステップ追加しますか?ここで典型的なものは何ですか?

さて、他のことについていくために長い一日を過ごした後、私はついに例を思いついた。それは理想的ではなく、決して素晴らしいものではないので、私にとっては喜ばしいことですが、うまくいけば、要点を理解するのに十分役立つはずです。私の現在のプロジェクトは複雑すぎて、これに単純なものを出すことができないので、私は何かを作り上げました...そして私を信じて...完全に作り上げました。

了解しました。例のオブジェクトは次のとおりです。

クライアント:クライアント側のコードを表す(コンソールアプリbtw)

IValidationInfo:これは、現在のプロジェクトで使用している実際のインターフェイスです。これにより、ビジネスロジックが十分に複雑になる可能性があるため、必ずしもクライアントが使用することを意図していない「バックエンド」オブジェクトの検証フレームワークを作成できます。これにより、検証コードを分離し、ビジネスロジックの必要に応じて呼び出すこともできました。

OrderManager:これは、クライアント側のコードが注文を管理するために使用できるオブジェクトです。それは、いわばクライアントフレンドリーです。

OrderSpecification:これは、クライアント側のコードが注文を要求するために使用できるオブジェクトです。しかし、ビジネスロジックが機能しない場合は、例外が発生する可能性があります(または、必要に応じて、順序が追加されず、例外が無視されます...)私の実際の例では、実際にはそれほど黒くないオブジェクトがあります。このフェンスのどちら側に行くかについては白です...したがって、検証要求(IsValidまたはValidate()を呼び出す)をcilentにプッシュできることに気付いたときの私の最初の質問。

CustomerDescription:私が分類した顧客を表します(DBから読み取られたふりをします)。

製品:分類されている特定の製品を表します。

OrderDescription:正式な注文リクエストを表します。ビジネスルールでは、顧客は分類されていないものを注文することはできません(私は知っていますが、それはあまり現実的ではありませんが、私に何かを提供してくれました...)

わかりました...ここにファイルを添付できないことに気付いたので、ここにコードを示します。見た目が長かったことをお詫び申し上げます。これは、検証インターフェイスを使用してクライアントフレンドリーなフロントエンドとビジネスロジックのバックエンドを作成するために私ができる最善の方法でした。

public class Client {static OrderManager orderMgr = new OrderManager();

    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
}
4

3 に答える 3

3

情報が有効な場合にコンストラクターがノイズを出して例外をスローしたくない理由はありますか?私の経験では、無効な状態でオブジェクトを作成することは避けるのが最善です。

于 2013-02-22T20:42:44.923 に答える
0

私の理解が正しければ、基本的に 2 つの質問があります。すぐに失敗するか後で失敗するか、および特定の情報を省略/仮定する必要があるかどうかです。

1) 私は常にできるだけ早く失敗することを好みます - 良い例はコンパイル時の失敗と実行時の失敗です - あなたは常にコンパイル時に失敗したいと思っています. したがって、ジョンが言ったように、オブジェクトの状態に何か問題がある場合-できる限り大声で例外をスローしてそれに対処します-if/elseif/elseifに向かっているので、今後さらに複雑になることはありません/elseif/else マンボ ジャンボ。

2)ユーザー入力に関しては、エラーを自動的に除外する立場にある場合は、それを実行してください。たとえば、ユーザーに国を尋ねることはほとんどありません。本当に必要な場合は、IP から自動的に検出してフォームに表示します。ユーザーがデータを確認/変更するだけでよい場合は、はるかに簡単です。null の状況に対処する必要はありません。

さて、何らかの処理中にコードによって生成されたデータについて話している場合-私にとって状況は劇的に異なります-私は常に可能な限り多くのことを知りたいと思っています(将来のデバッグを容易にするため)。情報。

まとめると、あなたの場合、IsValid を単純な yes/no (yes/no/maybe/kindaok/etc ではなく) にしておくことをお勧めします。いくつかの問題を自動的に修正できる場合は、修正してください。ただし、オブジェクトが IsValid に保持されていることを考慮してください。それ以外の場合は、例外をスローして IsValid=no に移動します。

于 2013-02-22T20:55:49.703 に答える
0

それは完全にクライアント次第です。あなたがすでに述べたようにトレードオフがあります。デフォルトでは、アプローチ番号 1 が私のお気に入りです。適切なカプセル化とクライアントからの詳細の非表示を備えたスマート クラスを作成します。賢さのレベルは、誰がオブジェクトを使用するかによって異なります。クライアントがビジネスを認識している場合、この認識のレベルに応じて詳細を明らかにできます。これは二分法であり、黒または白として扱われるべきではありません。

于 2013-02-22T21:01:44.257 に答える