设计模式——装饰者模式(Decorator)

装饰模式:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。有时我们希望给某个对象而不是整个类添加一些功能。

类继承可能会带来类数量爆炸等问题,设计死板。

巴克莱咖啡,分店几乎开遍世界各地。他们发展的实在是太快了,所以他们此时正在急于实现一套由计算机管理的自动化记账系统。在第一次研究了他们的需求以后,开发者设计了如下图的类结构:

        Beverage是所有饮料的基类; cost()是抽象方法,所有子类都需要定义它们自己的 cost()实现来返回特定饮料的价钱; description变量也是在子类里赋值的,表示特定饮料的描述信息, getDescription()方法可以返回这个描述;  

       除了咖啡以为,Central Perk还提供丰富的调味品,比如:炼乳、巧克力、砂糖、牛奶等,而且这些调味品也是要单独按份收费的,所以调味品也是订单系统中重要的一部分。

       于是,考虑到调味品的管理,开发者又有了下面这样的类结构:

       看了上面的类图,你一定有话要说!是的!这简直是太恐怖了,好像是整个类图都要爆炸了一样,而且以后随便增加一种调味品,继承于Beverage的子类还会翻倍!(因为理论上可能的咖啡种类数 = 咖啡类别数×调味品类别数) 我的神啊!全球变暖后连咖啡都“沸腾”了吗?还是我们在研制某种自杀式炸弹装置! ( 方便携带,不宜察觉,居家旅游必备,只要将几种调味品混合到一起,就能产生惊人的爆炸力!不错啊! J )  

       上面的情况绝对是不能容忍的,于是开发者们经过讨论,又提出了下面的设计方案件:

       如图所示,这是改进后的 Beverage基类。首先在基类里增加了表示是否包含特定调味品的布尔变量,如 milksoy等,然后提供了一些 has(get)set方法来设置这些布尔值;其次在 Beverage类里实现 cost()方法来计算调味品的价钱。所有咖啡子类将仍然覆盖 cost()方法,只是这次它们需要同时调用基类的 cost()方法,以便获得咖啡加上调味品后的总价。  

       看上去似乎这是一个不错的设计,那么下面我们再来给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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值