设计模式打怪升级之装饰器模式

来源:互联网 发布:运动健康软件下载 编辑:程序博客网 时间:2024/06/05 20:59

开篇语


        学习设计模式有一段时间了,这也不是所学习的第一个设计模式,但是却是落笔总结的第一个模式,个中原因很明显,之前一直觉得该把学过的东西进行思考加以总结,算是一个归纳整理的过程,但是却只是停留在想法的层面,没有实际行动。俗话说,万事开头难,当你迈出第一步,才会有后面的千步、万步,直到理想的彼岸,但若是一直想想,却没勇气和恒心迈出第一步,那就只能永远在原地打转。

        废话不多说,直接进入正题。

装饰器模式


        个人感觉3w学习法,对于学习和思考设计模式来说,是非常适合的,在此将按照what(什么是XX设计模式)-> why(为什么要使用XX设计模式)-> How(怎么使用XX设计模式)这样的思路对设计模式进行学习。

What -- 什么是装饰器模式


维基百科给出了装饰器模式的基本原理:
维基百科-装饰器模式

装饰器模式:增加一个装饰类包裹原对象,包裹的方式一般是通过将原来的对象作为装饰类的构造函数的参数,并且装饰类同所修饰对象都必须继承自同一个基类,因此,装饰类和所修饰对象具有相同的接口,但实现不同的功能。对于使用者,这些都是透明的。


Why -- 为什么要使用装饰器模式


在最初学习C++的时候,觉得继承和多态就是整个世界,而学习过设计模式之后,才发现,最初的自己实在是图样图森破- -!
继承固然是C++中的核心 + 重中之重,但并不是万金油,很多时候,在系统设计过程中,继承显得非常力不从心(当然,你也可以在这些情况下,使用继承,但是设计出来的系统,我只能呵呵了)。

举个例子 -- 选自《Head First 设计模式》
对于一个咖啡连锁店(就是说星巴克、Costa这样的地方)的订单管理系统,我们知道,咖啡有很多种,比如摩卡、拿铁、香草拿铁.....等等等等,而且不同用户可能会有不同的特定需求,比如我就喜欢拿铁加两份奶,若是采用将所有的咖啡的公共属性提取出来,构成一个基类。然后所有的咖啡都继承自基类,并分别根据自己的特征实现相应的属性和处理函数,并不是不可以,但是会存在三个主要的问题:
1、咖啡的种类很多,很容易造成“类爆炸”,如下图所示


2、对于咖啡店,顾客就是上帝,你必须能灵活的满足顾客的所有需求。若是碰到奇葩顾客,需要加4份奶、3份糖的拿铁,怎么办?有人会说,可以在系统设计之初就把这些可能的情况加进去。但是!!!谁能保证可以想到所有可能的情况。既然做不到,那怎么办?依据上面的继承的方法,我们只能修改现有代码,在使用过程中,每遇到一次系统中没有的情况,就修改代码,加入相应的处理过程。这样的系统,我想,没人愿意使用。这显然违背了软件设计中的封装变化原则(在软件设计过程中,应尽可能的把系统中不变的和可能发生改变的部分分离开,将变化的部分封装成固定的接口呈现给不变的部分,使得即使可变化的部分内部处理发生变化,系统中不变的部分依然可以不加修改的正确运行)。

3、若是配料的价格变了怎么办?难道我要一一修改使用该配料的咖啡子类的代码吗?

由上面,我们可以看出,在软件设计过程中,继承并不是万金油般的存在,在很多应用场景下,继承不是一个理想的选择。这其实就引出了软件设计中的另一条原则:
多用组合,少用继承

而装饰器模式设计的目标,基本上是基于解决上述咖啡馆问题的,即如何在不修改现有代码的情况下,就可搭配新的行为。这样的设计有什么好处呢?
这样的设计具有弹性,可以应对改变,可以接受新的功能来应对改变的需求。

由装饰器模式,我们可以引出第三条软件设计原则:
类应该对扩展开放,对修改关闭
即在设计一个类的时候,应尽可能的保证,若是需要添加新功能时,可以以该类的扩展的形式添加进系统,而不应该是需要修改现有的类代码。这将大大提升系统的灵活性和稳定性,并降低系统维护成本。

通过使用装饰器模式,可以在运行时扩充一个类的功能。 

装饰者模式的关系类图如图:


对应到咖啡馆问题时,可以这样设计:



到此,应该了解了为什么要使用装饰器模式了。因为我们想构建一个,对扩展开放,对修改关闭,具有弹性,可以应对改变,在不修改现有代码的情况下,就可搭配新行为的系统。


How -- 怎么使用装饰器模式


有上面装饰器模式的实现类图,可以了解到,装饰器模式系统,大抵由五部分组成:1、所要装饰对象的抽象类;2、所要装饰对象的具体实现类(派生自1);3、装饰器抽象类;4、具体装饰器类(派生自3);5、一个抽象基类(装饰器和所要装饰的对象都继承自此类,以保证对外提供以相同的接口 -- 无论用装饰器与否,对外部程序来说是透明的)。

具体的使用方法见代码说明:

// 饮料抽象基类的定义,包含有所有咖啡共有的属性和方法,和上面的Beverage对应class Beverage{public:std::string description;Beverage(){description = "Unknown Beverage";}virtual std::string getDescription(){return description;}virtual double cost() = 0;};
// 深度烘焙咖啡,继承自Beverage基类class DarkRoast : public Beverage{public:DarkRoast(){description = "Dark Roast Coffee";}virtual double cost();};
<pre name="code" class="cpp">// 深度烘焙cost函数具体实现double DarkRoast::cost(){return 0.99;}
// 低咖啡因,继承自Beverage基类class Decaf : public Beverage{public:Decaf(){description = "Decaf Coffee";}virtual double cost();};
<pre name="code" class="cpp">// 低咖啡因cost的具体实现double Decaf::cost(){return 1.05;}
// 浓缩,继承自Beverage基类class Espresso : public Beverage{public:    Espresso(){description = "Espresso";}virtual double cost();};
<pre name="code" class="cpp">// 浓缩咖啡cost函数的具体实现double Espresso::cost(){return 1.99;}
// 综合,继承自Beverage基类class HouseBlend : public Beverage{public:HouseBlend(){description = "House Blend Coffee";}virtual double cost();};
<pre name="code" class="cpp">// 综合cost函数的具体实现double HouseBlend::cost(){return 0.89;}
// 装饰器抽象基类,继承自Beverage,使得装饰器和被装饰的对象有相同的接口,达到对调用者透明class CondimentDecorator : public Beverage{public:virtual std::string getDescription() = 0;};
// 牛奶装饰器的具体定义class Milk : public Beverage{public:Milk(Beverage *beverage){m_beverage = beverage;description = "Milk";}virtual std::string getDescription();virtual double cost();private:Beverage *m_beverage;};
<pre name="code" class="cpp">// 牛奶装饰器的具体实现std::string Milk::getDescription(){return m_beverage->getDescription() + " " + description;}double Milk::cost(){return 0.10 + m_beverage->cost();}
// 摩卡装饰器的具体定义class Mocha : public CondimentDecorator{public:Mocha(Beverage *beverage){m_beverage = beverage;description = "Mocha";}virtual std::string getDescription();virtual double cost();private:Beverage *m_beverage;};
<pre name="code" class="cpp">// 摩卡装饰器的具体实现std::string Mocha::getDescription(){return m_beverage->getDescription() + " " + <span style="font-family: Arial, Helvetica, sans-serif;"> </span><span style="font-family: Arial, Helvetica, sans-serif;">description;</span>}double Mocha::cost(){return 0.20 + m_beverage->cost();}
// 豆浆装饰器的具体定义class Soy : public Beverage{public:Soy(Beverage *beverage){m_beverage = beverage;description = "Soy";}virtual std::string getDescription();virtual double cost();private:Beverage *m_beverage;};
// 豆浆装饰器的具体实现std::string Soy::getDescription(){return m_beverage->getDescription() + " " + description;}double Soy::cost(){return 0.15 + m_beverage->cost();}

可以看出来,在装饰器的方法中,会调用所装饰对象的相应的方法,这其实是实现装饰器模式的核心之所在。
以上,我们采用装饰器模式,实现了一个咖啡订单管理系统,可以看到,我们可以随意的将调料和咖啡进行组合,极其灵活。同时,若是引入新品种的咖啡或者调料,只要按照当前结构,实现相应的子类即可。并且,若是调料或者咖啡的价格发生变动,我们只需要修改相应的子类里的数据即可,非常便于扩展。
下面为测试代码:

#include <iostream>#include "HouseBlend.h"#include "DarkRoast.h"#include "Decaf.h"#include "Espresso.h"#include "Mocha.h"#include "Milk.h"#include "Soy.h"int main(){Beverage *beverage = new Espresso();std::cout << beverage->getDescription() << std::endl;Beverage *beverage2 = new DarkRoast();beverage2 = new Mocha(beverage2);beverage2 = new Soy(beverage2);std::cout << beverage2->getDescription() << beverage2->cost() << std::endl;Beverage *beverage3 = new HouseBlend();beverage3 = new Milk(beverage3);beverage3 = new Mocha(beverage3);beverage3 = new Soy(beverage3);std::cout << beverage3->getDescription() << beverage3->cost() << std::endl;return 0;}

总结

1、继承属于扩展形式之一,但不见得是达到弹性设计的最佳方式;
2、在软件设计中,应该允许行为可以被扩展,而无须修改现有的代码;
3、组合和委托可用于在运行时动态地加上新的行为;
4、装饰器可以在被装饰者的行为前后与|或后面加上自己的行为,甚至将被装饰者的行为整个取代掉,而达到特定的目的;
5、可以用无数个装饰器包装一个组件;
6、装饰器一般对组件的客户是透明的,除非客户程序依赖组件的具体类型(指依赖于具体的派生类类型,这时可能无法再使用基类类型的指针来对组件进行引用);
7、装饰器模式会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂。


0 0
原创粉丝点击