创建装饰器。设计模式

时间:2013-09-03 10:24:45

标签: c# design-patterns

假设定义了两个简单的装饰器:

// 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);

2 个答案:

答案 0 :(得分:3)

这可以使用IOC容器或类似的东西来解决。我头脑中突然出现的一些容器是UnityWindsorSimpleInjector。我会把IOC容器留给其他答案,因为我没有经验。

但是,我真的很想知道为什么要注入原生值。

看到如何使用类,在注释构造函数中注入折扣百分比或x购买免费项目这样的注入值会感觉很奇怪。

如果用户输入10(百分比)而不是0.1(十进制)作为折扣参数怎么办?它含糊不清。另外,如果在构造函数中添加检查,则会对该类赋予另一个责任,违反SRP。

我建议添加DiscountPercentValueBuyXGetYFreeValue等DTO。此外,我更喜欢将折扣值设置为上下文,或者为其注入存储库。否则,有一天您需要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创建为protects its invariants的类,从而使其在其他类中使用更安全。

答案 1 :(得分: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())。

到目前为止,我还没有添加任何东西,只有基本价格类。我接下来可能会做的是使用抽象类并将特定操作推迟到子类,如模板方法模式中所示。我的具体类将继承自这个基类。创建的具体类将取决于传递给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);
    }
}

该类和前两个方法是抽象的,需要由子类实现。第三种方法只是调用两个抽象方法,并将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());
        }
    }
}

我要考虑的另一种模式是策略方法模式。在这种情况下,Context类将所有计算推迟到其抽象类或接口引用的“ConcreteStrategy”。我发现Scott Millet的书“Professional ASP.NET Design Patterns”作为一种有用的资源并经常回归它。

我愿意接受评论和批评。

相关问题