zoukankan      html  css  js  c++  java
  • 基于东北F4的设计模式情景剧——第一幕 装饰模式(Decorator Pattern)

    第一场 难题未解

    布景:铁岭,晴天,午后,风。在一幢还算气派的写字楼的三层外墙上,挂着一条红色横幅,上面用歪歪扭扭的毛笔字写着“东北F4软件外包工作室”。大风中,那早已褪色的条幅剧烈地抖动着,发出阵阵嘶吼。房间内,东北F4正在为大鹏科技股份有限公司开发一款“大侠”游戏。

    刘能(坐在椅子上,扭头冲众人):好……好啦,搞……搞定!我创建了一个大侠虚基类,然后派生出了三峡和张凤霞两……两个实例,每个大侠有两个功能:攻击和隐……隐身。这是我画的那什么U……U什么图:

    刘能:还有,这是我写的代……代码:

     1 class DaXia   //虚基类  
     2 {  
     3 public:  
     4     DaXia() {};  
     5     virtual ~DaXia() {};  
     6     virtual void GongJi() = 0;  
     7     virtual void YinShen() = 0;  
     8 };  
     9   
    10 class SanXia :public DaXia   //三峡版本  
    11 {  
    12 public:  
    13     SanXia() {};  
    14     virtual ~SanXia() {};  
    15     virtual void GongJi()  
    16     {  
    17         cout << "来自三峡的攻击!" << '
    ';  
    18     }  
    19     virtual void YinShen()  
    20     {  
    21         cout << "三峡已隐身!" << '
    ';  
    22     }  
    23 };  
    24   
    25 class ZhangFengXia :public DaXia   //张凤霞版本  
    26 {  
    27 public:  
    28     ZhangFengXia() {};  
    29     virtual ~ZhangFengXia() {};  
    30     virtual void GongJi()  
    31     {  
    32         cout << "来自张凤霞的攻击!" << '
    ';  
    33     }  
    34     virtual void YinShen()  
    35     {  
    36         cout << "张凤霞已隐身!" << '
    ';  
    37     }  
    38 };

    (众人围拢过来看)

    宋小宝:那啥,刚才大鹏给我打电话了,说每个大侠除了攻击和隐身两个核心功能外,还可能会有一些小的辅助功能,比如在攻击之前先跳一下,或者在隐身之前先喊话。之所以说可能,是因为对特定的大侠而言,可能完全具备这些辅助功能,也可能只具备一部分,还可能一点都不具备。总之,就是不确定,你们看,这咋整?

    赵四(一脸得意):有什么不好整的,太简单了,让我来!

    (两分钟后……)

    赵四(愈发得意,将一张纸拍在众人面前):看吧!简直完美!

    赵四(摇头晃屁股):我设计的新类聚合了DaXia类,每个新类实现自己的动作,而原有的动作复用所聚合的类的代码。由于聚合的是大侠类的基类DaXia,所以所有的具体大侠类都可以被新类聚合,即新类适用于每一个具体大侠类,这在一定程度上减少了新增类的数量。假设我们需要一个在攻击前跳跃的三峡,只要用TiaoDaXia类的实例聚合一个SanXia类的实例就可以了;再比如,假设我们需要一个在攻击前跳跃,在隐身前喊话的张凤霞,只要用一个TiaoShuoDaXia类的实例聚合一个ZhangFengXia类的实例就可以了。你们说,我的设计是不是很棒……

    小沈阳(没等赵四说完,就一巴掌扇到他的左脸上):棒你个脑袋!你设计的是啥玩意!你设计的新张凤霞类根本就不是大侠类的子类,这在逻辑上本身就不合理!更重要的,你看看我客户端的接口,是这样式儿的:

    void DaXiaDongZuo(DaXia *daxia)  
    {  
            daxia->GongJi();  
            daxia->YinShen();  
    }

    看着没?你说你设计的破玩意,叫我怎么用?我得改代码,得改,知道不!要是明儿需求有了新的变化,照你那么设计,我还得改,还得改,知道不!(伸手指着赵四)你软件开发是体育老师教的啊!知道啥叫“开闭原则”不?面向扩展开放,面向修改关闭!

    赵四(一手捂着脸,一手指着小沈阳,愤怒而胆怯):你干嘛打我!不好就不好吧,你凭什么打我!(作发狠状)小样,这事没完,没完,知道不!今天这事必须有个了结!有种你给我等着,你给我十分钟,十分钟后,我保证——保证给你一个满意的方案!

    (十分钟后)

    赵四(战战兢兢走到小沈阳面前,颤抖着把一张纸拍在桌子上,马上又往后跳了一大截):小样,看好了!这回你满意了吧!这回你服了吧!你说,服不服!

    (众人谁都没有理会赵四,全凑过去看赵四的新方案)

    赵四(神气活现地):这回服了吧!我采用派生子类的方式,完美地解决了你刚才说的所有问题。怎么样,我是不是很机智……

    刘能(还没等赵四说完,一巴掌扇在他的右脸上):机智你个脑袋!他的问题解决了,我的呢?现在需要一个既跳又说的张凤霞,我就得新派生一个类,接下来,只跳不说的一个,只说不跳的一个,这就是三个;三峡下边,又是三个,这就六个了。如果明天还有新的需求出现,我还不得累吐血了啊!你来编码,你试试!这么多类,你叫我怎么维护?再说,你这是最简单,也是最笨、最愚蠢的做法!你发现了吗,对于跳这个功能,大家都是一样的,而你的设计,却不得不每个类都写一遍!一点复用都没有!

    赵四(哭丧着脸走向宋小宝):宝啊,他们都欺负我,你说,要扩展一个类的方法,不就是聚合和继承吗?可他们……呜……宝啊……(作拥抱状)

    宋小宝(一把推开赵四,极其轻蔑地)瞧你那损色!净想些找抽的方法!该!

    赵四(委屈地):没想到连你也欺负我!(不服地)你还说我呢,有本事你想一个好办法!

    宋小宝(心虚地):我要是有……有办法,还轮得着你挨打吗?

    小沈阳(不耐烦地):行了行了,到下班时间了,今天先这样吧。大家各回各家,各找各妈。回去都想想,看这事咋整。

    众人(无奈地):好吧,也只有如此了。

    (众人下,传来赵四的嘟囔声:你们都给我听好了,这事没完,我的打不会白挨,明儿,我一定叫你们服服帖帖的……)

    第二场 赵四逆袭

    布景:打了一夜麻将的宋小宝揉着惺忪的睡眼晃晃悠悠走进工作室,见其他三个人正围在一起说着什么……

    宋小宝(疑惑地):咋地啦,啥事呀?

    赵四(神气活现、摇头晃脑,提着一张纸走到宋小宝面前):宝啊,看着没?哥夜以继日、通宵达旦、彻夜未眠、左思右想、绞尽脑汁、搜肠刮肚,终于在黎明的曙光中想出了完美的解决方案!怎么样,服不服?

    宋小宝(仰起头发出一阵魔性的笑声):哈哈哈哈哈哈……这一大早上班,就听到这么好笑的笑话,(转向赵四,轻蔑地)瞧你那损色!你要是把这事摆平了,我就请大伙吃饭……

    刘能、小沈阳(挥拳作胜利状):耶!

    宋小宝(瞬间蒙圈):不是……咋……咋地?(疑惑地望着刘能和小沈阳,指着赵四)他真想出来了?

    刘能、小沈阳(冲宋小宝肯定地点头):这回是真的!
    宋小宝(心虚而疑惑地):等……等会儿,让我好好看看。

    (众人的目光又聚集到赵四的设计上)

    (好几分钟后)

    宋小宝(疑惑地望着赵四):你这设计的什么玩意啊,跟天书似得!

    赵四(撇嘴):瞧你那损色!这都看不懂!

    宋小宝(厉声):别抢我台词!有本事,你给解释一下。

    (赵四用期待的目光望向刘能和小沈阳)

    刘能(假装没看见,催促道):行啦,赶紧说吧,宝儿都等不及了。

    小沈阳:大家鼓掌!

    (众人鼓掌)

    赵四(故作姿态地清了清嗓子):嗯嗯!我们看,在我们设计的游戏中,攻击和隐身是主要的、核心的功能,而攻击前跳跃、隐身前喊话是依托核心功能而存在的、是对核心功能的扩展和补充。可以说,跳跃和喊话是对核心功能的一种装饰。

    众人(点头):有道理!

    赵四(大模大样地徘徊在众人中间):所以,我从大侠类派生出一个装饰大侠(ZhuangShiDaXia)类,专门处理经过装饰后的大侠的动作。

    刘能(举手打断):等一下!这不是和昨天一样吗?还得写好多好多类。

    赵四(伸出右手食指在众人面前摇晃,同时缓慢地摇头):No、No、No,非也非也。大家仔细看,我把每种装饰都作为ZhuangShiDaXia类的子类。例如,在我们的问题中,有跳跃和喊话两种装饰,于是我从ZhuangShiDaXia类派生出跳大侠(TiaoDaXia)和说大侠(ShuoDaXia)两个子类,我们不妨称之为具体装饰类。每个具体装饰类都只实现自己的装饰功能,而其它功能则采用daxia指向的对象的版本。以TiaoDaXia为例,在它的GongJi功能中,先调用自己的Tiao功能,而具体的GongJi功能和该装饰无关的核心功能,如YinShen功能,则调用daxia指向的对象的版本。

    刘能(不解地):daxia又是个什么鬼?

    赵四(神秘而庄重地):然后就是重点了。不知大家是否注意到,ZhuangShiDaXia类除了派生自DaXia类,还聚合了DaXia类,即ZhuangShiDaXia类中有一个DaXia*类型的指针daxia。再回过头来看,由于ZhuangShiDaXia类派生自DaXia类,所以,daxia可以指向上图中的所有类,包括ZhuangShiDaXia类的子类。

    小沈阳(一脸迷惑):这很重要吗?

    赵四(激动地):太重要了!以ZhuangShiDaXia类的子类TiaoDaXia类为例,很显然,它继承了ZhuangShiDaXia类的daxia指针,而这个指针,还可以指向ZhuangShiDaXia类的子类,比如ShuoDaXia类,而ShuoDaXia类的daxia指针又可以指向其它DaXia类的子类……这样,就可以实现类的“层次嵌套”,也可以理解为一种递归,于是,就可以实现功能的自由组合和调用时的“委托”。换句话说,你想要什么样的大侠,就可以组合出什么样的大侠,简直是变化万千,无穷无尽!哎呀,不能再说了,再说下去,我都佩服死自己了,哈哈哈哈!

    宋小宝(迷惑地):好像明白了,又好像不明白,能举个例子吗?

    赵四(胸有成竹地):当然能啦,我已经写好了一个Demo,大家请上眼!

      1 #include<iostream>
      2 
      3 using namespace std;
      4 
      5 class DaXia   //抽象基类
      6 {
      7 public:
      8     DaXia() {};
      9     virtual ~DaXia() 
     10     {};
     11     virtual void GongJi() = 0;
     12     virtual void YinShen() = 0;
     13 };
     14 
     15 class SanXia :public DaXia   //三峡类
     16 {
     17 public:
     18     SanXia() {};
     19     virtual ~SanXia() 
     20     {};
     21     virtual void GongJi()
     22     {
     23         cout << "来自三峡的攻击!" << '
    ';
     24     }
     25     virtual void YinShen()
     26     {
     27         cout << "三峡已隐身!" << '
    ';
     28     }
     29 };
     30 
     31 class ZhangFengXia :public DaXia   //张凤霞类
     32 {
     33 public:
     34     ZhangFengXia() {};
     35     virtual ~ZhangFengXia() 
     36     {};
     37     virtual void GongJi()
     38     {
     39         cout << "来自张凤霞的攻击!" << '
    ';
     40     }
     41     virtual void YinShen()
     42     {
     43         cout << "张凤霞已隐身!" << '
    ';
     44     }
     45 };
     46 
     47 class ZhuangShiDaXia :public DaXia   //装饰大侠类
     48 {
     49 protected:
     50     DaXia *daxia;   //聚合DaXia类的直接或间接子类
     51 public:
     52     ZhuangShiDaXia() {};
     53     ZhuangShiDaXia(DaXia *dx)
     54         :daxia(dx)
     55     {}
     56     virtual ~ZhuangShiDaXia()
     57     {
     58         delete daxia;
     59         daxia = NULL;
     60     }
     61     virtual void GongJi()   //由所聚合的类实现功能
     62     {
     63         daxia->GongJi();
     64     }
     65     virtual void YinShen()
     66     {
     67         daxia->YinShen();
     68     }
     69 };
     70 
     71 class TiaoDaXia :public ZhuangShiDaXia   //具有跳装饰的大侠类
     72 {
     73 protected:
     74     virtual void Tiao()   //实现跳装饰
     75     {
     76         cout << "跳一下" << '
    ';
     77     }
     78 public:
     79     TiaoDaXia() {};
     80     TiaoDaXia(DaXia *dx)
     81         :ZhuangShiDaXia(dx)
     82     {}
     83     virtual ~TiaoDaXia()
     84     {}
     85     virtual void GongJi()
     86     {
     87         Tiao();
     88         daxia->GongJi();   //具体的攻击功能交由所聚合的类完成
     89     }
     90 
     91     //YinShen功能完全采用基类版本,即daxia->YinShen(),还是交由所聚合的类完成
     92 };
     93 
     94 class ShuoDaXia :public ZhuangShiDaXia   //具有说装饰的大侠
     95 {
     96 protected:
     97     virtual void Shuo()   //实现说装饰
     98     {
     99         cout << "你来找我呀?!" << '
    ';
    100     }
    101 public:
    102     ShuoDaXia() {};
    103     ShuoDaXia(DaXia *dx)
    104         :ZhuangShiDaXia(dx)
    105     {}
    106     virtual ~ShuoDaXia()
    107     {}
    108     virtual void YinShen()
    109     {
    110         Shuo();
    111         daxia->YinShen();   //具体的隐身功能交由所聚合的类完成
    112     }
    113 
    114     //GongJi功能完全采用基类版本,即daxia->GongJi(),还是交由所聚合的类完成
    115 };
    116 
    117 int main()
    118 {
    119     ZhangFengXia *zfx = new ZhangFengXia();   //张凤霞
    120     ShuoDaXia *sdx = new ShuoDaXia(zfx);   //将张凤霞“嵌入”到具有说装饰的大侠实例中,构造出具有说装饰的张凤霞
    121     TiaoDaXia *tdx = new TiaoDaXia(sdx);//将具有说装饰的张凤霞“嵌入”到具有跳装饰的大侠实例中,使得最终的大侠具有跳装饰和说装饰
    122     tdx->GongJi();//攻击
    123     tdx->YinShen();//隐身
    124     delete tdx;
    125     tdx = NULL;
    126     sdx = NULL;
    127     zfx = NULL;
    128 
    129     return 0;
    130 }

    赵四(洋洋得意地):来,走一波!(赵四运行了程序)

    赵四(自豪地):看到了吧,我们构造出的大侠具有了跳装饰和说装饰。

    刘能(懵懂地):还是不太懂……

    赵四(故作不耐烦):好吧,我就再启发你一下。(又拿出一张纸)

    赵四:如上图所示,ZhangFengXia实例嵌入到ShuoDaXia实例中(即ShuoDaXia的daxia成员指向ZhuangFengXia实例),ShuoDaXia实例嵌入到TiaoDaXia实例中。当执行tdx->GongJi()时,执行代码

    virtual void GongJi()  
    {  
        Tiao();  
        daxia->GongJi();  //具体的攻击功能交由所聚合的类完成   
    }

     即先调用Tiao(),输出“跳一下”,然后执行daxia指向的实例的GongJi()函数,而此时的daxia(即this->daxia)指向的是ShuoDaXia实例,而ShuoDaXia类中的GongJi()函数依然采用的是从它的基类ZhuangShiDaXia继承过来的GongJi()函数,即依然执行daxia->GongJi(),而此时的daxia指向ZhangFengXia实例,于是调用ZhangFengXia类的GongJi()函数,输出“来自张凤霞的攻击!”。

     tdx->YinShen()的执行类似。先执行TiaoDaXia类的YinShen()函数,而该函数完全继承自基类ZhuangShiDaXia,于是执行

    virtual void YinShen()  
    {  
        daxia->YinShen();  
    }

    而此时daxia指向ShuoDaXia实例,于是调用ShuoDaXia实例的YinShen()函数,即

    virtual void YinShen()  
    {  
        Shuo();  
        daxia->YinShen();   //具体的隐身功能交由所聚合的类完成  
    }

    于是先调用Shuo(),输出“你来找我呀?!”,然后再调用daxia指向的实例的YinShen()函数,而此时的daxia指向ZhangFengXia实例,于是调用ZhangFengXia类的YinShen()函数,输出“张凤霞已隐身!”。

    众人(恍然大悟):哦,明白了!
    小沈阳(若有所思):也就是说,具体装饰类只具体实现自己的装饰部分,而其它的,都交由它所聚合的类完成。例如,对TiaoDaXia而言,它只实现具体的跳功能,并将其封装进自己版本的GongJi()函数中,而具体的攻击行为和不由它装饰的隐身行为,则统统采用它所聚合的类的版本。
    宋小宝(抢着道):更为关键的是,如果被聚合者也同样含有daxia指针,即是ZhuangShiDaXia的子类的话,它还会聚合其它的类,直到被聚合者不含daxia指针,如SanXia类和ZhangFengXia类。对于赵四给出的Demo,可以抽象出这样的“类的嵌套模型”。(举起手中的一张纸)

    刘能:没……没错!(指着赵四画的UML类图)通过继承和聚合,DaXia类和ZhuangShiDaXia类构成一个环,于是就可以实现类的“递归嵌套”了,而“递归出口”就是不含daxia指针的类。
    小沈阳:而这种“递归嵌套”的过程,就是功能组合的过程。分析赵四给出的Demo,不难看出,从代码的层面来看,嵌套,或者说功能组合是由内而外的,而调用是由外而内的。如果调用到的方法是具体装饰类自己的版本,则先执行相关的装饰功能,然后将核心功能交给“下一层”,这样“层层委托”,直到未经装饰的类

    刘能(激动地):我彻……彻底懂了,我还能举……举一反反……三呢,看,如果想要一个在攻击之前跳两次的大侠,这……这么写就可……可以了。(说着敲出如下代码)

    ZhangFengXia *zfx = new ZhangFengXia();   //张凤霞  
    TiaoDaXia *tdx1 = new TiaoDaXia(zfx);   //跳一下  
    TiaoDaXia *tdx = new TiaoDaXia(tdx1);   //再跳一下  

    赵四(摸着刘能的头):孺子可教也!

    刘能(扒拉开赵四的手):边去!别摸我头!

    赵四(故作高深地):其实,还有更简洁的写法。(敲出如下代码)

    TiaoDaXia *tdx = new TiaoDaXia(new TiaoDaXia(new ZhangFengXia()));  
    赵四:这么写更加简洁,但不太好懂,所以一开始我是分开写的。

    小沈阳(若有所思):你别说,赵四整的这个“装饰模式”,还挺好的。我们知道,当需要对一个类的功能进行扩展时,一般有聚合和继承两种方式,前者可以减少新增加的类的数量,但可能会带来与客户端接口不兼容的问题,需要修改客户端;后者虽然保持了接口的一致性,但在变化有多种组合时,子类数量激增。装饰模式同时采用继承和聚合,两者相辅相成、相生相克,既保持了各自的优点,又克服了对方的缺点,更重要的是,由于同时使用了继承和聚合,可以动态地实现类的层次嵌套和功能的自由组合,非常的灵活

    刘能:没……没错!当我们需要对核心功能进行装饰,而这些装饰又有很多变化和组合的时候,采用装饰模式是极好的。而且,当增加新的变化时,完全不用修改当前的代码,只需要增加一个具体装饰类就可以。也就是说,我们可以很方便地、随时随地地扩展功能,简直了,还有谁?very good!

    宋小宝(不屑地撇嘴):得了,消停会儿吧,又不是你发明的!

    赵四(故作深沉地):任何事物都有两面性。拿装饰模式来说,刚才大家都说了它的优点,却忽略了它的不足。与单纯的继承相比,装饰模式减少了项目中类的数量,但在具体应用时,却增加了客户端创建的对象的数量。假设我们需要一个在攻击前跳一下,在隐身前喊话的张凤霞,如果采用继承的方法,我们会从ZhangFengXia类派生一个具有相应功能的NewZhangFengXia类,客户端只需要创建一个NewZhangFengXia类的实例就可以了;而如果采用装饰模式,正如大家在我写的Demo里看到的,由于我们要组合、拼凑出相应的大侠,所以需要创建ZhangFengXia类的实例、ShuoDaXia类的实例、TiaoDaXia类的实例,共计3个实例。如果我们的类很大很复杂,那么,创建类的实例是比较耗时的,这样会影响系统效率。另外,装饰模式中对象之间是层层嵌套的,这就使得最终组合出来的对象比较复杂,一个调用会引起由外而内的一连串调用,一旦出现问题,我们往往不知道问题具体出现在哪个环节,只能逐层排查,显然这是很麻烦的。再者,正如大家看到的,在装饰模式下,客户端构造大侠实例的代码的可读性比较差,对于一个不懂设计模式的人来说,是比较难看懂这些代码的。所以,总的来说,在一定程度上,装饰模式会降低项目的效率,增加项目的复杂度

    小沈阳:可以呀,赵四,没看出来啊!有两把刷子!

    (响起深沉伤感的音乐)

    赵四(缓慢而深沉地):谢谢!(泪眼朦胧地眺望着远方)记得那是2010年,家里拿钱让我上《非诚勿扰》。我刚一上场,24盏灯就呼啦全灭了。从此,我就开始了屡败屡战的相亲征途。直到第999次相亲失败,我总算醒悟了,就我这副尊容,去相亲,说白了就是浪费感情。万念俱灰之下,我拿相亲剩下的钱,买了一台二手电脑,从此踏上了程序员这条不归路。我不像你们,都是加里敦、地理赛这些名牌大学的高材生,我是半路出家。于是,我是时时受欺负、处处被鄙视,可是(使劲挥拳),我不甘心!虽然我还没有媳妇,但我也是个男人!我也有尊严!所以,我一定要用实力证明自己!于是,我千方百计、处心积虑、不择手段地学习!终于,皇天不负苦心人,我做到了!

    小沈阳(揉着眼睛,呜咽着说):太感人了!四儿呀,对不起,以前是大哥不好,以后,大哥再也不欺负你了!

    刘能(擤了把鼻涕):太……太励志了!四儿呀,对……对不起,以前是二……二哥不好,以后,二哥再也不鄙……鄙视你了!

    (宋小宝悄悄向门口退去,却不小心碰倒了垃圾桶,于是被小沈阳和刘能发觉)

    小沈阳、刘能(追着跑在前面的宋小宝):宋小宝,你给我站住!

    (完)

  • 相关阅读:
    Oracle expdp impdp中 exclude/include 的使用
    Oracle表空间迁移Move Tablespace
    m2ewtp的作用
    有意思的排序算法合并排序
    有意思的排序算法插入排序
    equals与“==”之惑
    简化的打印语句静态导入
    有意思的排序算法堆排序
    博客开篇语
    MySQL 整理笔记 张大哥
  • 原文地址:https://www.cnblogs.com/zpcdbky/p/5939793.html
Copyright © 2011-2022 走看看