装饰者模式可以在不修改任何底层代码的情况下,给对象赋予新的职责(使用对象组合的方式,在运行时装饰类)。
假定星巴兹咖啡需要更新订单系统,而他们原先类的设计如图:
现在他们考虑客户可以选择添加调料(蒸奶,豆浆,摩卡等)到这几种咖啡中。
实现一:每种调料和咖啡的组合都形成一个新类,然后覆盖cost方法。这样会造成“类爆炸”,使得维护起来特别困难(假设需要新增加一种口味,那么类成几何倍数增长。如果要改变一种调味的价格,也需要修改许多的类)
实现二:利用实例变量和继承来追踪这些调料。
可以通过set方法来设置添加调料的种类,然后在基类的cost计算调料的价格。子类中的cost再调用父类的cost并加上自己的价格。
实现二的设计存在以下的一些问题:
- 一旦出现新的调料,我们就需要添加新的方法,并改变超类中的cost方法。
- 对以后开发出的新饮料而言,某些调料可能并不适合,但子类仍将继承这些方法。
- 顾客想要双倍摩卡咖啡。
(利用继承设计子类的行为,是在编译时静态决定的,而且所有子类都会继承到相同行为。然而,如果利用组合的做法扩展对象行为,就可以在运行时动态进行扩展)
设计原则:类应该对扩展开放,对修改关闭
对修改关闭是因为,现有代码是正确的,如果允许修改,则容易引入bug。
实现三:装饰者模式
- 拿一个DarkRoast对象
- 以摩卡对象装饰它
- 以奶泡对象装饰它
- 调用cost方法,并依赖委托将调料价格加上
装饰者模式动态地将责任附加到对象上。若想要扩展功能,装饰者提供了比继承更有弹性的替代方案。
- 装饰者和被装饰对象有相同的超类型
- 可以用一个或多个装饰者包装一个对象
- 装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装的)的场合,可以用装饰过的对象替代
- 装饰者可以在被装饰者行为之前/之后,加上自己的行为
- 对象可以在任何时候被装饰,可以运行时动态地,不限量地用装饰者装饰
(利用继承来达到“类型匹配”,而不是利用继承获得“行为”)
class Beverage
{
private:
String description = "Unknown Beverage";
public:
virtual String getDescription()
{
return description;
}
virtual double cost() = 0;
};
class Espresson : public Beverage
{
public:
Espresson()
{
description = "Espresson";
}
double cost()
{
return 1.99;
}
};
class Mocha : public Beverage
{
private:
Beverage* beverage;
public:
Mocha(Beverage* beverage)
{
this->beverage = beverage;
}
String getDescription()
{
return beverage->description() + ".Mocha";
}
double cost()
{
.20 + beverage->cost();
}
};
// 测试代码
int main()
{
// 不知道这样内存都删干净了没有,或者用智能指针更好
Beverage* beverage = new Espresson();
Beverage* beverage1 = new Mocha(beverage);
Beverage* beverage2 = new Whip(beverage1);
delete beverage;
delete beverage1;
delete beverage2;
}
真实世界的装饰者:
LineNumberInputStream->BufferedInputStream->FileInputStream
从Java.io可以看到装饰者模式的一个“缺点”,设计中有大量的小类存在。
总结:
- 装饰者和被装饰对象要具有相同的超类型(在任何需要原始对象(被包装的)的场合,可以用装饰过的对象替代)
- 装饰者中存储一个被装饰者