装饰模式:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。有时我们希望给某个对象而不是整个类添加一些功能。
类继承可能会带来类数量爆炸等问题,设计死板。
巴克莱咖啡,分店几乎开遍世界各地。他们发展的实在是太快了,所以他们此时正在急于实现一套由计算机管理的自动化记账系统。在第一次研究了他们的需求以后,开发者设计了如下图的类结构:
除了咖啡以为,Central Perk还提供丰富的调味品,比如:炼乳、巧克力、砂糖、牛奶等,而且这些调味品也是要单独按份收费的,所以调味品也是订单系统中重要的一部分。
于是,考虑到调味品的管理,开发者又有了下面这样的类结构:
上面的情况绝对是不能容忍的,于是开发者们经过讨论,又提出了下面的设计方案件:
看上去似乎这是一个不错的设计,那么下面我们再来给Beverage增加子类,如下图所示:
基类的cost()方法将计算所有调味品的价钱(当然是只包括布尔值为true的调味品),子类里的cost()方法将扩展其功能,以包含特定类型饮料的价钱。
OK! 现在我们似乎已经有了一个看上去还不错的设计,那么Central Perk的这个记账系统就按这个设计来实现就万事大吉了吗?等一下,还是让我们先从以前学习过的“找到系统中变化的部分,将变化的部分同其它稳定的部分隔开。”这个设计原则出发,重新推敲一下这个设计。
那么对于一家咖啡店来说,都有那些变化点呢?调味品的品种和价格会变吗?咖啡的品种和价格会变吗?咖啡和调味品的组合方式会变吗?YES! 对于一家咖啡店来说,这些方面肯定会经常发生改变的!那么,当这些改变发生的时候,我们的记账系统要如何应对呢? 如果调味品发生改变,那么我们只能从代码的层次重新调整Beverage基类,这太糟糕了;如果咖啡发生改变,我们可以增加或删除一个子类即可,这个似乎还可以忍受;那么咖啡和调味品的组合方式发生改变呢?如果顾客点了一杯纯黑咖啡外加两份砂糖和一份巧克力,或者顾客点了一杯脱咖啡因咖啡(Decaf)外加三份炼乳和一份砂糖呢?我倒!突然意识到,上面的设计根本不支持组合一份以上同种调味品的情况,因为基类里的布尔值只能记录是否包含某种调味品,而并不能表示包含几份,连基本的功能需求都没有满足,看来这些开发者可以卷铺盖滚蛋了!
好吧!让我们来接手这个设计!我们已经分析了前面设计的失败之处,我们应该实现支持调味品的品种和价格任意改变而不需要修改已有代码的设计;我们还要实现支持咖啡品种和价格任意改变而不需要修改已有代码的设计(这点上面的设计通过继承算是实现了);还有就是支持咖啡和调味品的品种和份数任意组合而不需要修改已有代码的设计;还有就是代码重用越多越好了,内聚越高越好了,耦合越低越好了;(还有最重要的,报酬越高越好啦!)
看来我们要实现的目标还真不少,那么我们到底该怎么做呢?说实话,我现在也不知道!我们需要先去拜访一下今天的主角—装饰者模式,看看她能给我们带来什么惊喜吧!
这就是装饰者模式
我们还是先看一下官方的定义:
The Decorator Pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality. (装饰者模式可以动态地给一个对象增加其他职责。就扩展对象功能来说,装饰者模式比生成子类更为灵活。)
这里我们要重点注意那个dynamically(动态的),什么是动态?静态又是什么?这是我们要重点区分的地方,后面我们还会专门讨论这个问题。下面先看看装饰者模式的类图和顺序图:
Decorator模式 咖啡店 c++ code
#include <iostream>
#include <string>
using namespace std;
//abstract Beverage
class Beverage
{
public:
virtual string getDescription(){ return "Unknown Beverage";}
virtual double cost()= 0;
};
//Espresso
class Espresso:public Beverage
{
public:
Espresso(){}
double cost(){return 1.99;};
string getDescription(){ return "Espresso";}
};
//HouseBlend
class HouseBlend:public Beverage
{
public:
HouseBlend(){}
double cost(){return .89;}
string getDescription(){ return "House Blend Coffee";}
};
//DarkRoast
class DarkRoast:public Beverage
{
public:
DarkRoast(){}
double cost(){return .99;}
string getDescription(){ return "DarkRoast";}
};
//Decaf
class Decaf:public Beverage
{
public:
Decaf(){}
double cost(){return 1.05;}
string getDescription(){ return "Decaf";}
};
//abstract CodimentDecorator
class CondimentDecorator:public Beverage
{
public:
CondimentDecorator(Beverage *be):beverage(be){}
protected:
Beverage *beverage;
};
//Mocha
class Mocha:public CondimentDecorator
{
public:
Mocha(Beverage *be):CondimentDecorator(be){}
string getDescription(){ return beverage->getDescription() + ", Mocha";}
double cost(){return 0.20 + beverage->cost();}
};
//Soy
class Soy:public CondimentDecorator
{
public:
Soy(Beverage *be):CondimentDecorator(be){}
string getDescription(){return beverage->getDescription() + ", Soy";}
double cost(){return 0.10 + beverage->cost();}
};
//Whip
class Whip:public CondimentDecorator
{
public:
Whip(Beverage *be):CondimentDecorator(be){}
string getDescription(){return beverage->getDescription() + ", Whip";}
double cost(){return 0.15 + beverage->cost();}
};
int main()
{
Beverage *beverage = new Espresso();
cout<<beverage->getDescription()<<"$"<<beverage->cost()<<endl;
Beverage *beverage2 = new DarkRoast();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
cout<<beverage2->getDescription()<<"$"<<beverage2->cost()<<endl;
Beverage *beverage3 = new HouseBlend();
beverage3 = new Soy(beverage3);
beverage3 = new Mocha(beverage3);
beverage3 = new Whip(beverage3);
cout<<beverage3->getDescription()<<"$"<<beverage3->cost()<<endl;
return 0;
}
小结 :
装饰者模式的优点:
1、 通过组合而非继承的方式,实现了动态扩展对象的功能的能力。
2、 有效避免了使用继承的方式扩展对象功能而带来的灵活性差,子类无限制扩张的问题。
3、 充分利用了继承和组合的长处和短处,在灵活性和扩展性之间找到完美的平衡点。
4、 装饰者和被装饰者之间虽然都是同一类型,但是它们彼此是完全独立并可以各自独立任意改变的。
5、 遵守大部分GRASP原则和常用设计原则,高内聚、低偶合。
装饰者模式的缺点:
1、 装饰链不能过长,否则会影响效率。
2、 因为所有对象都是Component,所以如果Component内部结构发生改变,则不可避免地影响所有子类(装饰者和被装饰者),也就是说,通过继承建立的关系总是脆弱地,如果基类改变,势必影响对象的内部,而通过组合(Decoator HAS A Component)建立的关系只会影响被装饰对象的外部特征。
3、只在必要的时候使用装饰者模式,否则会提高程序的复杂性,增加系统维护难度。
参考资料:
《Headfirst Design Pattern》
王晓亮 / Justin http://www.cnblogs.com/justinw/archive/2007/06/11/779356.html#2626284