装饰者模式
从我们之前学到的知识中, 已经知道扩展一个类的方法是为他派生新的子类, 也就是通过继承来扩展功能.
然而, 使用继承是静态的, 在编译的时候就已经决定了子类的行为, 我们不便于控制增加行为的方式和时机.
而装饰者模式可以动态地将责任附加到对象上, 若要扩展对象, 装饰者模式提供了比继承更弹性的替代方案.
使用场景:对象由主体+许多可选的部件或者功能构成,使用继承或者接口会产生很多类,且很难扩展。例如,现在需要一个汉堡,主体是鸡腿堡,可以选择添加生菜、酱、辣椒等等许多其他的配料,这种情况下就可以使用装饰者模式。
装饰模式的UML图:
● 抽象构件(Component)类:给出一个抽象接口, 以规范准备接收附加责任的对象.
● 具体构件(ConcreteComponent)类:被修饰的者, 是定义了一个具体的对象, 可以给这个对象添加一些职责(也就是装饰它).
● 装饰(Decorator)类:抽象装饰类, 维护一个构件(Component)对象的实例, 定义一个与抽象构件接口一致的接口.
● 具体装饰(ConcreteDecorator)类:具体装饰类, 负责给构件对象"贴上"附加的责任.
它的C++模型如下(技术有限, 可能实现的有问题, 欢迎指正):
#include <iostream> using namespace std; class Component { public: virtual char* Operation() = 0; protected: char m_description[256]; }; class ConcreateComponent : public Component { public: ConcreateComponent() { strcpy_s(m_description, 255, "初始状态"); } char* Operation() { return m_description; } }; class Decorator : public Component { public: Decorator(Component* pComponent) { m_pComponent = pComponent; } virtual char* Operation() = 0; protected: Component* m_pComponent; }; class ConcreteDecoratorA : public Decorator { public: ConcreteDecoratorA(Component* pComponent) : Decorator(pComponent) { strcat_s(m_pComponent->Operation(), 255, " --> 装饰了 A ‘饰品‘"); } char* Operation() { return m_pComponent->Operation(); } }; class ConcreteDecoratorB : public Decorator { public: ConcreteDecoratorB(Component* pComponent) : Decorator(pComponent) { strcat_s(m_pComponent->Operation(), 255, " --> 装饰了 B ‘饰品‘"); } char* Operation() { return m_pComponent->Operation(); } }; int _tmain(int argc, _TCHAR* argv[]) { Component* test = new ConcreateComponent(); cout << test->Operation() << endl; Component* test_A = new ConcreteDecoratorA(test); cout << test_A->Operation() << endl; Component* test_A_B = new ConcreteDecoratorB(test_A); cout << test_A_B->Operation() << endl; system("PAUSE"); return 0; }
输出如下:
看下它的优点:
1、装饰者模式可以提供比继承更多的灵活性
2、可以通过一种动态的方式来扩展一个对象的功能,在运行时选择不同的装饰器,从而实现不同的行为.
3、通过使用不同的具体装饰类以及这些装饰类的排列组合, 可以创造出很多不同行为的组合. 可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象.
4、具体构件类与具体装饰类可以独立变化, 用户可以根据需要增加新的具体构件类和具体装饰类, 在使用时再对其进行组合, 原有代码无须改变, 符合"开闭原则".
它的缺点也是一样的明显:
1、会产生很多的小对象, 增加了系统的复杂性
2、这种比继承更加灵活机动的特性, 也同时意味着装饰模式比继承更加易于出错, 排错也很困难, 对于多次装饰的对象, 调试时寻找错误可能需要逐级排查, 较为烦琐.
实际开发情景:星巴克以扩张速度快而闻名, 在里面购买咖啡时, 可以要求在其中加入各种调料, 星巴克会根据所加入的调料收取不同的费用, 也就是说不同的咖啡与调料之间有N多不同的组合方式. 每种咖啡和调料都有不同的收费. 如果他们此时正在急于实现一套由计算机管理的自动化记账系统, 在这个时候我们使用继承方式, 则会陷入无以复加的地步, 这里会有N多个类, 出现“类爆炸”现象.
此时就可以实用我们的装饰者模式:
具体代码省略, 参考:http://blog.chinaunix.net/uid-20761674-id-304542.html
模式的简化
如果只有一个ConcreteComponent类, 那么可以考虑去掉Cpmponent类, 让Decorator直接继承ConcreteComponent类, 重写ConcreteComponent的Operation方法.
同样, 如果只有一个ConreteDecortor类, 那么也可以去掉单独的Decorator类, 把Decorator和ConreteDecortor的合并.
1 #include <iostream> 2 using namespace std; 3 4 class ConcreateComponent { 5 public: 6 ConcreateComponent() { 7 strcpy_s(m_description, 255, "初始状态"); 8 } 9 10 virtual char* Operation() { 11 return m_description; 12 } 13 14 protected: 15 char m_description[256]; 16 }; 17 18 class Decorator 19 : public ConcreateComponent { 20 public: 21 Decorator(ConcreateComponent* pComponent) { 22 m_pComponent = pComponent; 23 } 24 25 virtual char* Operation() = 0; 26 27 protected: 28 ConcreateComponent* m_pComponent; 29 }; 30 31 class ConcreteDecoratorA 32 : public Decorator { 33 public: 34 ConcreteDecoratorA(ConcreateComponent* pComponent) : Decorator(pComponent) { 35 strcat_s(m_pComponent->Operation(), 255, " --> 装饰了 A ‘饰品‘"); 36 } 37 38 char* Operation() { 39 return m_pComponent->Operation(); 40 } 41 }; 42 43 class ConcreteDecoratorB 44 : public Decorator { 45 public: 46 ConcreteDecoratorB(ConcreateComponent* pComponent) : Decorator(pComponent) { 47 strcat_s(m_pComponent->Operation(), 255, " --> 装饰了 B ‘饰品‘"); 48 } 49 50 char* Operation() { 51 return m_pComponent->Operation(); 52 } 53 }; 54 55 int _tmain(int argc, _TCHAR* argv[]) { 56 ConcreateComponent* test = new ConcreateComponent(); 57 cout << test->Operation() << endl; 58 59 ConcreateComponent* test_A = new ConcreteDecoratorA(test); 60 cout << test_A->Operation() << endl; 61 62 ConcreateComponent* test_A_B = new ConcreteDecoratorB(test_A); 63 cout << test_A_B->Operation() << endl; 64 65 system("PAUSE"); 66 return 0; 67 }