为了理解装饰器模式,让我们考虑一个灵感来自“Head First Design Pattern”一书的场景。假设我们正在为一家比萨店构建一个应用程序,我们需要对他们的比萨类进行建模。假设他们提供四种类型的比萨,即 Peppy Paneer、Farmhouse、Margherita 和 Chicken Fiesta。最初我们只是使用继承并抽象出Pizza类中的通用功能。
每个披萨都有不同的成本。我们已经覆盖了子类中的 getCost() 以找到合适的成本。现在假设一个新的需求,除了一个pizza,客户还可以要求几个配料,如Fresh Tomato、Paneer、Jalapeno、Capsicum、Barbeque等。让我们考虑一下,我们如何适应上述类的变化这样客户可以选择带配料的比萨饼,我们得到客户选择的比萨饼和配料的总成本。
让我们看看各种选择。
选项1
为每个披萨配料创建一个新子类。类图如下所示:
这看起来非常复杂。课程太多了,这是一场维护噩梦。此外,如果我们想添加新的配料或披萨,我们必须添加很多类。这显然是非常糟糕的设计。
选项 2:
让我们向比萨基类添加实例变量来表示每个比萨是否有浇头。类图如下所示:
超类的 getCost() 计算所有配料的成本,而子类中的 getCost() 计算特定比萨饼的成本。
// Sample getCost() in super class
public int getCost()
{
int totalToppingsCost = 0;
if (hasJalapeno() )
totalToppingsCost += jalapenoCost;
if (hasCapsicum() )
totalToppingsCost += capsicumCost;
// similarly for other toppings
return totalToppingsCost;
}
// Sample getCost() in subclass
public int getCost()
{
// 100 for Margherita and super.getCost()
// for toppings.
return super.getCost() + 100;
}
这种设计起初看起来不错,但让我们来看看与之相关的问题。
- 浇头的价格变化将导致现有代码的更改。
- 新的浇头将迫使我们添加新方法并更改超类中的 getCost() 方法。
- 对于某些比萨饼,某些浇头可能不合适,但子类继承了它们。
- 如果客户想要双辣椒或双芝士爆裂怎么办?
简而言之,我们的设计违反了最流行的设计原则之一——开闭原则,该原则规定类应该对扩展开放,对修改关闭。
在下一组中,我们将介绍装饰器模式并将其应用于上述问题。
参考资料: Head First Design Patterns(书籍)。