1,装饰者可以在被装饰的行为前面或者后面添加新的行为,甚至于完全替换掉。
2,装饰者会导致很多小对象的产生,不可过度使用。
3,通过组合和委托,可以在运行时候动态添加新的行为。
4,装饰者模式遵循面向扩展开放,面向修改关闭的原则。
5,装饰者模式通过组合方式包装被装饰者,从而扩展被装饰者。
真实世界的装饰者:
java.io类。通过层层包装实现增加功能
基本架构:
一个抽象类下有一些基本的单元(具体类),有一些基本的装饰因子。通过将基本单元和装饰因子组合起来,就可以形成复杂的单元。同时复杂的单元又可以再次被装饰,从而功能变得更加强大。所有的基本单元以及经过装饰的复杂单元都统一继承于一个抽象类,从而实现类型统一。这会让我们想到组合模式以及上节讲述命令模式的时候的MacroCommand。
宏命令本身是一种组合模式的体现,这里的装饰者也是一种组合模式的体现。只是宏命令里面是一个链表,保存这命令的集合,装饰者更侧重于对某一个单元进行装饰,因此对于装饰者类一般只需要传递一个抽象类指针就可以了,而宏命令传递的是一个命令集合。装饰者强调给现有的东西新增功能,宏命令强调是利用原有的一堆功能合并在一起组成功能列表。装饰者里面一般是对一个现有的单元的行为增加一个新的方法来扩展功能,宏命令一般是直接调用一堆命令的action,将一堆命令封装组合成一个命令。与宏命令相似的还有目录的树结构,典型的组合模式思想,以及观察者模式中Subject同时也是观察者的时候的类图结构。
装饰者模式:
大圈套小圈,一圈接一圈。
组合模式:
小圈组大圈,大小圈组大圈。
//基类抽象类。 class Light { public: Light(){ } public: virtual string GetInfo() = 0; }; //基本单元BulbLight,电灯泡,它们后期可能需要扩展新的功能 class BulbLight : public Light { public: BulbLight(){ } publc: string GetInfo(){ return "BulbLight"; } }; //基本单元LampLight,白炽灯 class LampLight : public Light { public: LampLight(){ } publc: string GetInfo(){ return "LampLight"; } }; //以下为装饰因子 //添加带颜色的功能 class ColorLight : public Light { public: ColorLight(Light* light){ this->m_pLight = light; } public: string GetInfo(){ return m_pLight->GetInfo() + " Color"; } private: Light* m_pLight; }; //添加会闪烁的功能 class BlinkLight : public Light { public: BlinkLight(Light* light){ this->m_pLight = light; } public: string GetInfo(){ return m_pLight->GetInfo() + " Blink"; } } //添加声控功能 class VoiceLight : public Light { public: VoiceLight(Light* light){ this->m_pLight = light; } public: string GetInfo(){ return m_pLight->GetInfo() + " Voice"; } } //通过将基本单元和装饰因子组合,可以形成功能强大的灯。 //例如创建一个闪烁又具有声控的电灯泡 BulbLight* pLight = new BulbLight; BlinkLight* pBlink = new BlinkLight(pLight); VoiceLight* pVoiceBlink = new VoiceLight(pBlink); cout<<pVoiceBlink->GetInfo()<<endl;
装饰者模式正如它的名字一样, 就是一个装饰而已。它主要是用于给现有的类增加一些新的功能,同时又不影响原有的接口。通常做法是有一个抽象类,例如上文的Light,它就是一个灯的抽象,它有一个功能叫做GetInfo,由于它是抽象的,故而一般像这样的方法也会定义为虚函数,便于子类进行自己的个性化。BulbLight和LampLight是具体的灯类,它有自己的GetInfo功能,但是现在需求增加了,需要声控的BulbLight灯。对于此,我们有两个方案,一个通过继承于BulbLight,添加声控功能同时再调用BulbLight的基本的GetInfo方法不就可以了,一个是通过上面的类似的方法,继承于抽象的Light,将要添加声控功能的BulbLight灯传递进去,同时保持GetInfo接口不变,通过组合复用原有功能并添加了新功能。
分析:
继承与组合的优缺点:如果你想要创建一个闪烁并具有声控功能的BulbLight灯,你就会发现继承有它的局限性。通过组合,你可以任意给现有的已有的功能增加任意想要添加的功能,而且是可以无限制的扩展。通过继承,当你只增加一个功能,你还看不出问题来,毕竟一个继承就搞定,当功能逐渐添加,你的类的继承层次会逐渐增大,而且当同时需要具有a和b和c的功能的时候,难道还需要多重继承?越往后扩展越被限制。这个明显违背了对扩展开放的原则。
装饰者模式优越点:
此种模式通过组合完成它的任务,为了维护代码的接口统一性,将新增功能的类同样继承于Light抽象类,并实现其相关接口;为了给现有的类增加功能,将现有的类的对象传递进去,通过组合委托的方式调用其旧的功能,可以在其后添加新的代码增加新的功能。
基本类结构:
Class Abstract { virtual void BasicFunction() = 0;//基本功能 }; Class OldConcrete : public Abstract { void BasicFunction(){//基本功能 //....过去的基本功能代码 } }; //过去的使用的地方: Abstract* pConcrete = new OldConcrete(); pConcrete.BasicFunction(); //现在来了新需求,需要扩展现有功能。 Class NewConcrete : public Abstract //为了保持接口统一,面向接口编程,继承于抽象基类 { public: NewConcrete(Abstract* pA){ this.m_pA = pA; } public: void BasicFunction(){ //1,假设需要先调用过去的旧功能 m_pA->BasicFunction(); //2, 调用扩展的新功能,通过这种方式就透明的增加了新的功能 ExtendFunction(); } private: void ExtendFunction(){ //这个是扩展的新功能的代码 } private: Abstract* m_pA; }; //现在的使用的地方: Abstract* pConcrete = new OldConcrete(); Abstract* pNewConcrete = NewConcrete(pConcrete);//只需要再包装一次而已 pNewConcrete.BasicFunction();//接口不要有任何变化,旧的OldConcrete没有任何变化,就偷偷的添加了新的功能。