尝试装饰器设计模式,这段代码有什么问题?

时间:2013-04-27 02:40:45

标签: c#-4.0 design-patterns

我有这个解释装饰器模式的代码:

 public abstract class IBeverage {
    protected string description = "Unknown beverage";

    public virtual string getDescription() {
        return description;
    }
}

public abstract class CondimentDecorator : IBeverage {
    public abstract string getDescription();
}

public class Espresso : IBeverage {
    public Espresso() {
        description = "Espresso";
    }
}

public class Mocha : CondimentDecorator {
    IBeverage beverage;

    public Mocha(IBeverage beverage) {
        this.beverage = beverage;
    }

    public override string getDescription() {
        return beverage.getDescription() + ", Mocha";
    }
}

我应该像以下一样使用它:

static void Main(string[] args) {
    IBeverage b = new Espresso();
    Console.WriteLine(b.getDescription());
    b = new Mocha(b);
    Console.WriteLine(b.getDescription());

    Console.ReadKey();
}

当我创建饮料时(Beverage b = new Espresso();)_description更新为“Espresso”,当我用Mocha(b =新摩卡(b))装饰b时,_description采用原始值“Unknown”饮料”。它应该是“Espresso,Mocha”。 出了什么问题?

这段代码最初是用Java编写的(这本书是用Java编写的),但我把它翻译成了C#。我猜Java与C#有点不同。

2 个答案:

答案 0 :(得分:4)

因为GetDescription()不是virtual

public virtual string GetDescription() { ... }

virtualoverride的伴随关键字,它允许子类覆盖方法。这是C#与Java的关键区别。在Java中,所有方法都是隐式虚拟的。

答案 1 :(得分:3)

你实际上在这里遇到了一些问题(也许是与Java不同的设计)。即使在对所有命名问题进行​​排序之后,您也无法获得预期的结果。

public abstract class CondimentDecorator : IBeverage {
    public abstract string GetDescription();
}
  • CondimentDecorator类实际上会隐藏IBeverage版本GetDescription()方法(技术上应该使用public new abstract string GetDescription();

  • 您正在将Mocha类分类为IBeverage,并将其分配给b变量(您之前通过IBeverage b = new Espresso()将其定义为IBeverage, IBeverage方法的GetDescription()版本实际触发(完全忽略了Mocha CondimentDecorator方法的GetDescription()覆盖)

如果您单步执行代码,则可以看到此内容。尝试使用

CondimentDecorator m = new Mocha(b);
Console.WriteLine(m.GetDescription());

你会得到你期望的。

然而,在我看来,这种方式违背了使用装饰器的目的。更好的选择是稍微改变设计并摆脱CondimentDecorator。除了混乱和意外行为之外,它不提供任何其他内容。相反,试试这个:

这是您唯一需要的抽象饮料课程:

public abstract class Beverage
{
    // c# convention is to use properties instead of public fields.
    // In this case I've used a private readonly backing field. 
    private readonly string _description = "Unknown Beverage";

    protected string Description
    {
        get { return _description; }
        set { _description = value; }
    }

    // Make this method virtual so you can override it, but if you
    // choose not to, this is the default behaviour.
    public virtual string GetDescription()
    {
        return Description;
    }
}

这是一种标准饮料类(可以装饰):

public class Espresso : Beverage
{
    public Espresso()
    {
        // Setting the Beverage class Description property.
        // You can use base.Description if you prefer to be explicit
        Description = "Espresso";
    }
}

这是一个装饰另一个Beverage类的Beverage类:

public class Mocha : Beverage
{
    // store an instance of the Beverage class to be decorated
    private readonly Beverage _beverage;

    // Beverage instance to be decorated is passed in via constructor
    public Mocha(Beverage beverage)
    {
        _beverage = beverage;
    }

    // Override Beverage.GetDescription
    public override string GetDescription()
    {
        // Calls decorated Beverage's GetDescription and appends to it.
        return _beverage.GetDescription() + ", Mocha";
    }
}

现在要获得您期望的行为,您可以运行与上面相同的代码:

static void Main(string[] args) 
{
    Beverage b = new Espresso();
    Console.WriteLine(b.getDescription()); // "Espresso"
    b = new Mocha(b);
    Console.WriteLine(b.getDescription()); // "Espresso, Mocha"

    Console.ReadKey();
}

作为旁注。使用 Ctrl + F5 进行调试时,可以避免使用 Console.ReadKey(); 这将自动放入{ {1}}为你。

更新

由于您想要包含CondimentDecorator类(如评论中所述),您可以创建以下类:

"Press any key to continue..."

然后,您可以将public abstract class CondimentDecorator : Beverage { private readonly Beverage _beverage; protected Beverage Bevy { get { return _beverage; } } protected CondimentDecorator(Beverage beverage) { _beverage = beverage; } } 课程更改为以下内容:

Mocha