1.概述
定义:
装饰模式是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。就是装饰类既继承自真实对象,又包含真实对象的引用。
意图:
动态地将一些额外的责任附加到对象上。就增加功能来说,Decorator模式相比生成子类更为灵活。
2.应用场景
以下情况使用Decorator模式
1)需要扩展一个类的功能,或给一个类添加附加职责。
2)需要动态的给一个对象添加功能,这些功能可以再动态的撤销。
3)需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变的不现实。
4)当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
3.优缺点
优点:
1)Decorator模式与继承关系的目的都是要扩展对象的功能,但是Decorator可以提供比继承更多的灵活性。
2)通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。
缺点:
1)这种比继承更加灵活机动的特性,也同时意味着更加多的复杂性。
2)装饰模式会导致设计中出现许多小类,如果过度使用,会使程序变得很复杂。
3)装饰模式是针对抽象组件(Component)类型编程。但是,如果你要针对具体组件编程时,就应该重新思考你的应用架构,以及装饰者是否合适。当然也可以改变Component接口,增加新的公开的行为,实现“半透明”的装饰者模式。在实际项目中要做出最佳选择。
4.要点:
1)装饰者和被装饰对象有相同的超类型。
2)可以用一个或多个装饰者包装一个对象。
3)装饰者可以在所委托被装饰者的行为之前或之后,加上自己的行为,以达到特定的目的。
4)对象可以在任何时候被装饰,所以可以在运行时动态的,不限量的用你喜欢的装饰者来装饰对象。
5)装饰模式中使用继承的关键是想达到装饰者和被装饰对象的类型匹配,而不是获得其行为。
6)装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型。在实际项目中可以根据需要为装饰者添加新的行为,做到“半透明”装饰者。
7)适配器模式的用意是改变对象的接口而不一定改变对象的性能,而装饰模式的用意是保持接口并增加对象的职责。
5.与继承的区别
6.代码
《大话设计模式C++版》中以T恤、大脚裤来装饰人的例子来描述装饰模式,我们也用这个例子来对类适配器模式和对象适配器模式进行描述。最后再对大话设计模式中给出的例子进行分析。
1)例一
首先贴出例子,后面的适配器模式、装饰模式都在这个例子基础上修改或扩展。
1 class Person
2 {
3 public:
4 virtual void Show()
5 {
6 cout<<"decorate Mr Wang"<<endl;
7 }
8 };
9
10 class TShirts
11 {
12 protected:
13 Person* m_component;
14 public:
15 virtual void Decorate(Person* component)
16 {
17 m_component=component;
18 }
19
20 virtual void Show()
21 {
22 cout<<"T Shirts"<<endl;
23 m_component->Show();
24 }
25 };
26
27 int _tmain(int argc, _TCHAR* argv[])
28 {
29 Person* pers = new Person();
30 TShirts* tshit = new TShirts();
31 tshit->Decorate(pers);
32 tshit->Show();
33
34 system("Pause");
35 return 0;
36 }
运行结果:
这个例子在TShirts类中定义Person类的指针,并用该指针调用Person类的成员函数Show,与对象适配器模式有点接近。
2)例二:类适配器模式
1 #include "stdafx.h"
2 #include <iostream>
3 using namespace std;
4
5 class Person //Target目标类
6 {
7 public:
8 virtual void Show()=0; // 原功能的接口
9 };
10
11 class TShirts //Adaptee适配类(新增的功能)
12 {
13 public:
14 virtual void SpecialShow()
15 {
16 cout<<"TShirts"<<endl;
17 }
18 };
19 // ConcretePerson为Adapter
20 class ConcretePerson:public Person,private TShirts // 私有继承适配类TShirts
21 {
22 public:
23 virtual void Show()
24 {
25 cout<<"给小王穿上:"<<endl; // 原功能接口实现
26 this->SpecialShow(); // 适配进新功能
27 }
28 };
29
30 int _tmain(int argc, _TCHAR* argv[])
31 {
32 Person* person = new ConcretePerson(); // Target* t = new Adapter();
33 person->Show();
34
35 system("Pause");
36 return 0;
37 }
运行结果:
这里定义了Person抽象类、继承自Person类的ConcretePerson具体类、TShirts类。
当我们希望ConcretePerson具体类和TShirts类两者的功能都要用到,且又不能修改两者的功能时,就可以用适配器模式来实现。即让ConcretePerson类通过私有继承TShirts类来获得其功能。
但是如果需要对例二进行扩展,比如”人”除了穿“T恤”外,还要给他穿上“裤子”、“鞋子”,就得修改TShirts类,给它添加“裤子“和”鞋子”的功能,这样违背了开闭原则。而且因为TShirts类既有”T恤”功能,又有“裤子”和“鞋子”的功能,违背了单一职责原则。当然可以将TShirts类抽象出来,具体的功能推迟到子类中实现。这里不做讨论。
3)例三:对象适配器模式
1 #include "stdafx.h" 2 #include <iostream> 3 using namespace std; 4 5 class Person //Target目标类 6 { 7 public: 8 virtual void Show()=0; // 原功能的接口 9 }; 10 11 class TShirts //Adaptee适配类(新增的功能) 12 { 13 public: 14 virtual void Show() 15 { 16 cout<<"TShirts"<<endl; 17 } 18 }; 19 // ConcretPerson为适配器, 20 class ConcretPerson :public Person 21 { 22 protected: 23 TShirts* m_component; 24 public: 25 ConcretPerson() 26 { 27 m_component = new TShirts(); 28 } 29 30 virtual void Show() 31 { 32 cout<<"给小王穿上:"<<endl; // 原功能接口实现 33 m_component->Show(); // 适配进新功能 34 } 35 }; 36 37 int _tmain(int argc, _TCHAR* argv[]) 38 { 39 Person * tshit = new ConcretPerson (); 40 tshit->Show(); 41 42 system("Pause"); 43 return 0; 44 }
注意,例三中,类Person(目标类)和TShirts(适配类)是两个不同的类,没有继承关系。所以两个类中的Show函数是不同的,不存在覆盖的关系。将两者命名成相同名字,是为了提供统一的接口,这样客户端就不需要关心是调用TShirts(适配类)的show还是ConcretePerson(被适配的对象)的show。即接口的合并。
4)例四:
同样,如果要让例三中“人”再穿上“裤子”和“鞋子”,可以在TShirts类的Show函数中添加(违背单一职责原则、开闭原则)。或者再添加BigTrouser类和Shoes类,并将两者适配进ConcretePerson类中。为方便说明,例四中把不同适配类中的Show函数区分开一下。
1 #include "stdafx.h" 2 #include <iostream> 3 using namespace std; 4 5 class Person //Target目标类 6 { 7 public: 8 virtual void Show()=0; // 原功能的接口 9 }; 10 11 class TShirts //Adaptee适配类 12 { 13 public: 14 virtual void ShowOne() 15 { 16 cout<<"TShirts"<<endl; //新增的功能 17 } 18 }; 19 20 class BigTrouser //Adaptee适配类 21 { 22 public: 23 virtual void ShowTwo() 24 { 25 cout<<"BigTrouser"<<endl; //新增的功能 26 } 27 }; 28 29 // ConcretPerson为适配器. 30 class ConcretPerson :public Person 31 { 32 protected: 33 TShirts* ComponentOne; 34 BigTrouser* ComponentTwo; 35 public: 36 ConcretPerson() 37 { 38 ComponentOne = new TShirts(); 39 ComponentTwo = new BigTrouser(); 40 } 41 42 virtual void Show() 43 { 44 cout<<"给小王穿上:"<<endl; // 原功能接口实现 45 ComponentOne->ShowOne(); // 适配进新功能 46 ComponentTwo->ShowTwo(); // 适配进新功能 47 } 48 }; 49 50 int _tmain(int argc, _TCHAR* argv[]) 51 { 52 Person * tshit = new ConcretPerson (); 53 tshit->Show(); 54 55 system("Pause"); 56 return 0; 57 }
可以看到以上依旧修改了ConcretePerson类,也就是说违背了开闭原则。怎么办呢,我们想到,适配器的一个优点便是:一个对象适配器可以把多个不同的适配者类适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。我们尝试把适配者类抽象出来,并且适配者类放到客户端实例化。
5)例五:多个适配者类适配到同一个对象
1 #include "stdafx.h" 2 #include <iostream> 3 using namespace std; 4 5 class Person //Target目标类 6 { 7 public: 8 virtual void Show()=0; // 原功能的接口 9 }; 10 11 class Adaptee 12 { 13 public: 14 virtual void Show()=0; 15 }; 16 17 class TShirts:public Adaptee //Adaptee适配类 18 { 19 public: 20 virtual void Show() 21 { 22 cout<<"TShirts"<<endl; //新增的功能 23 } 24 }; 25 26 class BigTrouser:public Adaptee //Adaptee适配类 27 { 28 public: 29 virtual void Show() 30 { 31 cout<<"BigTrouser"<<endl; //新增的功能 32 } 33 }; 34 35 // ConcretPerson为适配器. 36 class ConcretPerson :public Person 37 { 38 protected: 39 Adaptee* m_adaptee; 40 public: 41 virtual void Decorate(Adaptee* adaptee) 42 { 43 m_adaptee = adaptee; 44 } 45 46 virtual void Show() 47 { 48 cout<<"给小王穿上:"<<endl; // 原功能接口实现 49 m_adaptee->Show(); // 适配进新功能 50 } 51 }; 52 53 int _tmain(int argc, _TCHAR* argv[]) 54 { 55 ConcretPerson * person = new ConcretPerson (); 56 TShirts* tshirts = new TShirts(); 57 person->Decorate(tshirts); 58 person->Show(); 59 BigTrouser* bigtrouser = new BigTrouser(); 60 person->Decorate(bigtrouser); 61 person->Show(); 62 63 system("Pause"); 64 return 0; 65 }
例五虽然解决了例四开闭性问题,但是也引起一个新的问题:每适配一次都会调用一次原功能,即输出结果中的“给小王穿上”。要解决这个问题我们就要用到装饰模式了。
6)例六:装饰者模式
#include "stdafx.h" #include <iostream> using namespace std; // 真实对象 class Person { private: string m_strName; public: Person(string strName) { m_strName=strName; } Person(){} virtual void Show() { cout<<"装扮的是:"<<m_strName<<endl; } }; //装饰类 class Finery :public Person { protected: Person* m_component; public: void Decorate(Person* component) { m_component=component; } virtual void Show() { m_component->Show(); } }; //T恤 class TShirts: public Finery { public: virtual void Show() { cout<<"T Shirts"<<endl; m_component->Show(); } }; //裤子 class BigTrouser :public Finery { public: virtual void Show() { cout<<" Big Trouser"<<endl; m_component->Show(); } }; //客户端 int main() { // 实例化父类 Person *p=new Person("小李"); BigTrouser *bt=new BigTrouser(); TShirts *ts=new TShirts(); // Person类中 // virtual void Show() // { // cout<<"装扮的是:"<<m_strName<<endl; // 6.输出"装扮的是:"+ m_strName // } bt->Decorate(p); // Big Trouser类 // virtual void Show() // { // cout<<" Big Trouser"<<endl; //4.输出" Big Trouser" // m_component->Show(); //5.因为前面bt->Decorate(p);这时的m_component为p,p->Show();跳到6 // } ts->Decorate(bt); // T Shirts类 // virtual void Show() // { // cout<<"T Shirts"<<endl; // 2.先输出"T Shirts" // m_component->Show();//3.因为前面ts->Decorate(bt);,这时 //的m_component为bt,bt->Show();跳到4 // } // 1. ts调用Show()函数, 跳到2 ts->Show(); system("Pause"); return 0; }
如注释所示,当ts->Show()时,进入TShirts类中Show函数,先输出"T Shirts",再调用m_component->Show();因为前面ts装饰了bt,所以m_component实际为bt,即指向BigTrouser类的指针,执行bt->Show();进入BigTrouser类中的Show函数,先输出" Big Trouser",再调用m_component->Show();因为bt装饰了p,m_component实际为p,进入Person类中的Show函数,输出人名。
装饰模式:
1)真实类(Person)、装饰类(Finary)、具体装饰类(TShirts、BigTrouser)为继承关系。
2)装饰类中有指向真实类的指针,根据Decorate函数的参数,可以指向真实类,也可以指向装饰类和具体装饰类(因为是继承关系,根据里氏代换原则,软件系统中,一个可用接受基类对象的地方必然可用接受一个子类对象。)
3)装饰类中有Show函数,由真实类的指针调用。若Decorate中传入的是真实类指针,则调用真实类中的Show函数,若传入的是具体装饰类,则调用具体装饰类的Show函数。
4)装饰了具体装饰类的真实类又可以作为指针传入Decorate中,从而实现功能的叠加。如例六中的注释。