2

2 つの単純なデコレータが定義されているとします。

// decorated object
class Product : IComponent {
    // properties..        
    // IComponent implementation
    public Decimal GetCost() {
        return this.SelectedQuantity * this.PricePerPiece;
    }
}

// decorators
class FixedDiscountDecorator : IComponent {
    IComponent component;
    // IComponent implemantation
    public Decimal GetCost() {
        // ...
    }
    public FixedDiscountDecorator(IComponent product, Decimal discountPercentage) {
        // ...
    }
}

class BuyXGetYFreeDiscountDecorator : IComponent {
    IComponent component;
    // IComponent implemantation
    public Decimal GetCost() {
        // ...
    }
    // X - things to buy
    // Y - things you get free
    public BuyXGetYFreeDiscountDecorator(IComponent product, Int32 X, Int32 Y) {
        // ...
    }
}

これらのデコレーターには、異なるコンストラクターのシグネチャ (パラメーター リスト) があります。ファクトリ パターンのようなデコレータを構築するために適用するパターンを探していました。つまり、文字列を入れてデコレータ インスタンスを取得します。

その結果、デコレータのチェーンを特定の製品に単純に適用したいと思います。

var product = new SimpleProduct {
    Id = Guid.NewGuid(),
    PricePerPiece = 10M,
    SelectedQuantity = 10,
    Title = "simple product"
};

var itemsToApplyTheDiscount = 5;
var itemsYouGetFree = 2;
var discountPercentage = 0.3M;

var discountA = new BuyXGetYFreeDecorator(product, itemsToApplyTheDiscount, itemsYouGetFree);
var discountB = new FixedDiscountDecorator(discountA, discountPercentage);
4

2 に答える 2

3

これは、IOC コンテナなどを使用して解決できます。頭に浮かんだコンテナのいくつかはUnityWindsorおよびSimpleInjectorです。経験がないので、IOCコンテナは他の回答に任せます。

しかし、なぜネイティブ値を注入するのか、本当に疑問に思います。

クラスがどのように使用されるかを見ると、コンストラクターに割引率や x 購入 y 無料アイテムなどの値を挿入するのは奇妙に感じられます。

ユーザーが割引パラメータとして 0.1 (小数) ではなく 10 (パーセント) を入力した場合はどうなるでしょうか? それはあいまいさを作ります。さらに、コンストラクターでチェックを追加すると、クラスに別の責任が与えられ、SRP に違反します。

DiscountPercentValueまたはなどの DTO を追加することをお勧めしBuyXGetYFreeValueます。さらに、割引の値がコンテキストとして設定されているか、そのために注入されるリポジトリがあることを好みます。factoriesそうしないと、いずれ割引に関連する if-else ビジネス ルールを処理する必要が生じるでしょう。

EDIT1:

通常、コンストラクターの検証はnullチェックのみとして保持します。それ以外の検証はcan be違反と見なされます。

リポジトリに関しては、次のようなインターフェイスを想像します。

public interface IDiscountPercentageProvider{
    DiscountValue Get();
}

public interface IBuyXGetYFreeValueProvider{
    BuyXGetYFreeValue Get();
}

次に、サービス クラスで、次のようなものを使用できます。

class FixedDiscountDecorator : IComponent {
    IComponent component;
    // IComponent implemantation
    IDiscountPercentageProvider discountPercentageProvider;
    public Decimal GetCost() {
        DiscountValue discount = discountPercentageProvider.Get();
        // ...
    }
    public FixedDiscountDecorator(IComponent product
        , IDiscountPercentageProvider discountPercentageProvider) {
        // ... just null checks here
    }
}

これは最初は複雑かもしれません。ただし、より優れた API 設計が提供されます (デコレータを使用する場合のあいまいさはなくなりました)。これを使用して、をその不変条件を保護DiscountValueするクラスとして作成し、他のクラスで使用することをより安全にすることができます。

于 2013-09-03T11:31:52.430 に答える
3

あなたの例では、特定の製品に適用されるデコレータのチェーンを示しています。

var discountA = new BuyXGetYFreeDecorator(product, itemsToApplyTheDiscount, itemsYouGetFree);
var discountB = new FixedDiscountDecorator(discountA, discountPercentage);

質問は、product上記のコードから構築された、指定されたプロパティによって管理される状態を変更するために使用できるいくつかのパターンは何ですか? あなたの例を使用して、私の範囲をproductコストの決定に限定します。

public interface IComponent
{
    decimal GetCost { get; set; }
}

IComponent を表す製品クラスを作成します

public class Product
{
    public IComponent Price { get; set; }
}

デコレータ クラスに加えて、「デフォルト」の実装がある場合があります。

public class BasePrice : IComponent
{
    private Decimal _cost;

    public decimal GetCost //as a property maybe use Cost with get; set; in IComponent
    {
        get { return _cost; }
        set { _cost = value; }
    }
}

IoC を使用し、IComponent からコストのバリエーション (GetCost()) を実装する 2 つのデコレータ クラスが気に入っています。

ここまでは、基本価格クラスのみで、何も追加していません。次に私ができることは、テンプレート メソッド パターンに示されているように、抽象クラスを使用し、特定の操作をサブクラスに任せることです。私の具体的なクラスは、この基本クラスから継承します。作成される具象クラスは、Factory メソッド パターンに渡されるアクション タイプに依存します。このクラスは、以下で WarrantyProcessFactory と呼ばれます。

あなたはapp.configを使用すると言いました...私は、適用されるアクションタイプを指定するために使用する列挙型が好きproductです。たとえば、製品の保証に基づいて製品を処理できるように、アクションを製品の保証に関連付けたいとします。

public enum WarrantyAction
{
    RefundProduct = 0,
    ReplaceProduct = 1
}

保証要求クラスを使用して、欠陥製品に対する顧客からの補償要求を表します

public class WarrantyRequest
{
    public WarrantyAction Action { get; set; }
    public string PaymentTransactionId { get; set; }
    public decimal PricePaid { get; set; }
    public decimal PostageCost { get; set; }
    public long ProductId { get; set; }
    public decimal AmountToRefund { get; set; }
}

最後に、保証アクション列挙型を表す具象クラスによってオーバーライドされる抽象テンプレート メソッドを実装できます。

public abstract class WarrantyProcessTemplate
{
    protected abstract void GenerateWarrantyTransactionFor(WarrantyRequest warrantyRequest);
    protected abstract void CalculateRefundFor(WarrantyRequest warrantyRequest);

    public void Process(WarrantyRequest warrantyRequest)
    {
        GenerateWarrantyTransactionFor(warrantyRequest);
        CalculateRefundFor(warrantyRequest);
    }
}

クラスと最初の 2 つのメソッドは抽象的であり、サブクラスによって実装する必要があります。3 番目のメソッドは、単純に 2 つの抽象メソッドを呼び出して、WarrantyRequest エンティティを引数として渡します。

お客様が返金を希望するケース

public class RefundWarrantyProcess : WarrantyProcessTemplate
{
    protected override void GenerateWarrantyTransactionFor(WarrantyRequest warrantyRequest)
    {
        // Code to determine terms of the warranty and devalutionAmt...
    }

    protected override void CalculateRefundFor(WarrantyRequest warrantyRequest)
    {
        WarrantyRequest.AmountToRefund = warrantyRequest.PricePaid * devalutionAmt;
    }
}

代替品をご希望の場合

public class ReplaceWarrantyProcess : WarrantyProcessTemplate
{
    protected override void GenerateWarrantyTransactionFor(WarrantyRequest warrantyRequest)
    {
        // Code to generate replacement order
    }

    protected override void CalculateRefundFor(WarrantyRequest warrantyRequest)
    {
        WarrantyRequest.AmountToRefund = warrantyRequest.PostageCost;
    }
}

public static class WarrantyProcessFactory
{
    public static WarrantyProcessTemplate CreateFrom(WarrantyAction warrantyAction)
    {
        switch (warrantyAction)
        {
            case (WarrantyAction.RefundProduct):
                return new RefundWarrantyProcess();
            case (WarrantyAction.ReplaceProduct):
                return new ReplaceWarrantyProcess();
            default:
                throw new ApplicationException(
                     "No Process Template defined for Warranty Action of " +
                                               warrantyAction.ToString());
        }
    }
}

私が検討するもう 1 つのパターンは、Strategy Method パターンです。この場合、Context クラスは、その抽象クラスまたはインターフェースによって参照される「ConcreteStrategy」にすべての計算を任せます。Scott Millet の著書「Professional ASP.NET Design Patterns」が有用なリソースであることがわかり、よく読み返しています。

私はコメントや批判を受け付けています。

于 2013-09-11T00:24:52.050 に答える