装饰模式能够实现动态的为对象添加功能,是从一个对象外部来给对象添加功能。在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
装饰器模式刚开始看非常难看懂。其实可以想象这种模式如同数据结构的链表,也可以想象成贪吃蛇。
一个类被不停地装饰,相当于在其尾不停地添加结点,最后在读取这个类的时候,犹如读取链表,由于next指针的存在,将会一直回溯读取内容直到next指针为空,从而达到滚雪球的效果。
一个类被装饰得越多,犹如贪吃蛇不停在吃食物导致蛇身越来越长,其内容就越来越丰富。
下面用一道2012年上半年软件设计师的软考题来说明这个例子。
题目是这样的:某咖啡店当卖咖啡时,可以根据顾客的要求在其中加入各种配料,咖啡店会根据所加入的配料来计算费用。咖啡店所供应的咖啡及配料的种类和价格如下表所示。
咖啡有两种:蒸馏咖啡Espresso 25元/杯,深度烘焙咖啡DarkRoast 20元/杯
可以在咖啡里面加配料:摩卡Mocha 10元/份,奶泡Whip 8元/份
现采用装饰器模式Decorator模式来实现计算费用的功能,得到如图5-1所示的类图。
具体实现代码如下所示:
#include <iostream>
#include <string>
using namespace std;
//定义各商品的价格
const int ESPRESSO_PRICE = 25;
const int DRAKROAST_PRICE = 20;
const int MOCHA_PRICE = 10;
const int WHIP_PRICE = 8;
class Beverage{//饮料
protected:
string description;
public:
virtual string getDescription(){//这里virtual不能省,不然找不到上一级的Beverage的了
return description;
}
virtual int cost()=0;
};
class CondimentDecorator : public Beverage{//配料
protected:
Beverage *beverage;//这个指针,用于回溯之前已经存在的内容
};
//两种咖啡,其构造函数皆没有Beverage *beverage;也就是说,这两个类只能作为基础,不能作为叠加上去的配件。
class Espresso : public Beverage{//蒸馏咖啡
public:
Espresso(){
description="Espresso";
}
int cost(){
return ESPRESSO_PRICE;
}
};
class DarkRoast : public Beverage {//深度烘焙咖啡
public:
DarkRoast(){
description = "DardRoast";
}
int cost(){
return DRAKROAST_PRICE;
}
};
//对比起上述两种“基类”,这两种东西构造函数皆存在Beverage *beverage;(Beverage *beverage;的声明源于对CondimentDecorator这个类的继承),其可以叠加
class Mocha : public CondimentDecorator {//摩卡
public:
Mocha(Beverage* beverage){
this->beverage=beverage;
}
string getDescription(){
return beverage->getDescription()+",Mocha";
}
int cost(){
return MOCHA_PRICE+beverage->cost();
}
};
class Whip :public CondimentDecorator {//奶泡
public:
Whip(Beverage* beverage){
this->beverage=beverage;
}
string getDescription() {
return beverage->getDescription()+",Whip";
}
int cost() {
return WHIP_PRICE+beverage->cost();
}
};
//主函数
int main() {
Beverage* beverage = new DarkRoast();
beverage=new Mocha(beverage);
beverage=new Whip(beverage);
cout<<beverage->getDescription()<<"¥"<<beverage->cost()<<endl;
return 0;
}
运行结果也正如题目要求的编译运行上述程序,其输出结果为:DarkRoast, Mocha, Whip¥38
上面的代码虽然不长,但极具深度。信息量极大。
先从主函数入手,相当于一个客人买了深度烘焙咖啡DarkRoast这种咖啡,然后为了喝得更爽,在里面加了摩卡Mocha与奶泡Whip,最后一共38元结账。
(1)主函数中,之所以一个beverage->getDescription()就能打印出三种东西,是因为,在新建奶泡Whip的时候,主函数的beverage类中已经包含了摩卡Mocha与深度烘焙咖啡DarkRoast。因为你在new摩卡Mocha的时候,你将深度烘焙咖啡DarkRoast扔到摩卡Mocha这个构造函数里面,然后摩卡Mocha自身的beverage指针指向深度烘焙咖啡DarkRoast。最后形成了Whip->"beverage"=Whip->Mocha->"beverage"=Whip->Mocha->DarkRoast的效果,beverage->getDescription()便能够一次打印这三个东西,价格同理,实质上DarkRoast、Mocha、Whip三个类就叠加起来了,DarkRoast被Mocha、Whip所装饰了。
不知道这样说,大家是否已经明白了。还可以想象成一条贪吃蛇,吃了DarkRoast之后又吃了Mocha、Whip,蛇身就从1变成了3。
(2)在class Beverage{}中virtual string getDescription(){}必须为主函数。因为这个函数其子类,不断地在同名方法重写,这时根据C++的特性,这个函数最后定义为虚函数,否则由于编译、引用的关系,其子类的这个同名方法,指针很可能指到父类去,亲测去掉后程序是错误的。反正一个函数被多态,加上虚函数就正确了,具体的理论,我也看得不太懂。