装饰者模式想要解决的是动态地扩展类的功能的问题。学过面向对象的同学都知道,面向对象的三大特性之一,继承,也是在解决类的扩展问题。我们将类共有的属性和操作放在父类里面,然后在子类里定义子类特有的属性和操作。继承使父类和子类之间产生了联系,当创建一个新类的时候,我们不需要从头开始,只需要在父类的基础上再创造子类。继承当然是扩展类功能的一种手段,但它也不是完美无缺的。继承会造成父类和子类之前的紧密耦合,子类的实现依赖于父类的实现,换句话说,就是子类的实现缺少自由。根据里氏替换原则,子类必须完全继承父类的方法和属性,如果后来发现某个子类和父类的设计原则出现冲突,可能会出现较大的代价。例如经典的“鸵鸟非鸟”问题,在大多数人眼里,鸟是会飞的,自然地,我们会把飞作为鸟的一种属性,但是有的鸟,例如鸵鸟,它是不会飞的,所以鸵鸟这个类里不应该具有飞的属性,但是如果鸵鸟是继承鸟这个类来的,它就会带有飞的属性。这会造成逻辑上的冲突。为了解决这个问题,可能会导致基类和一系列子类的代码需要修改。继承的另外一个缺陷是,当很多不同的类具有相同功能时,比如说,很多动物都具有飞行的能力,在设计时,我们可能会在不同的动物类内部实现相同的功能,造成设计上的冗余。                 装饰者模式试图从另一个角度,不通过继承,来解决类功能扩展的问题。装饰在这里的可以理解为添加功能和属性的意思。装饰者模式分为两个部分,装饰者和被装饰者,装饰者为被装饰者提供新的功能和属性,使被装饰者被转变为一个新的对象。这样,我们就可以实现对象和功能的分离。如果我们想让一个对象具有不同的功能,就让不同的装饰器来装饰它。相比继承,装饰器模式的扩展性更好,因为在继承关系里,功能是由对象决定的,子类会继承父类的功能,等于说子类只能在父类的基础上作修改,而新增加的功能取决于于装饰器,而不再取决于对象本身,当我们希望改变功能时,我们可以保持被装饰者不变,而更换装饰器。这样可以同一对象实现很多不同的功能。同样,装饰器模式也让功能的复用更加简单,因为不同的被装饰者使用相同的装饰器就有相同的功能。

 

装饰者模式的UML图

 

 

 

 

组件(component):抽象的被装饰者,是最基础的组件。

具体组件(concrete component):具体的被装饰者,继承自组件。

装饰器(decorator):装饰器为组件提供新的功能。

具体装饰器(concrete decorator):代表能够某种新功能的装饰器。

  从另一个角度,使用装饰者模式可以将对象和功能进行分离,对象保留那些本身独有的,和自己紧密相连的属性和功能。那些非自身独有的,与特定对象相关度小的功能,应该放入装饰器当中去,使其能被不同的对象复用。下面以一个游戏为例子,进一步说明装饰者模式。

  在一个游戏内,可能存在各种职业,例如法师,战士,刺客等。有一些技能是所有职业共有的,可以把这些技能提取出来,装封在角色这个组件了。这个组件是一个基类。

 

class actor{
    int life;
    int strength;
    int speed;
    int endurance;
public:
    //所有角色共用的技能
    actor();
    virtual ~actor();
    void talk();
    void run();
    void walk();
    void attack();
};

 

  在这个抽象类之外,不同的职业会有一些不同的技能,由此会派生出不同的子类,这些子类就是具体的职业。在基类共有的技能之外,又有自己特有的技能,而且这些技能是自己所独有的。

 

//职业是具体的角色,它们各自拥有一些特殊的技能
class warrior :public actor{
public:
    warrior();
    void combo();
};


class wizard :public actor{
public:
    wizard();
    void curse();
};


class assassin : public actor{
public:
    assassin();
    void assassinate();
};


warrior::warrior(){
    printf("该角色是一名战士\n");
}

void warrior::combo(){
    printf("发动连击\n");
}

wizard::wizard(){
    printf("该角色是一名法师\n");
}

void wizard::curse(){
    printf("发动诅咒\n");
}

void assassin::assassinate(){
    printf("发动暗杀\n");
}
assassin::assassin(){
    printf("该角色是一名刺客\n");
}

 

 

 

  然后,在游戏里面,有一些道具,比如武器,符文啊,这些是任意职业都可以使用的。所以可以选择将这些功能用装饰器来实现,以实现代码的复用。装饰器还是继承自组件且包含一个组件,代表原来的组件经过装饰器装饰以后,成为了具有某种新功能的组件。我们把所有装饰器都能提供的功能提取出来,创造一个抽象的装饰器。

//用装饰器来实现武器的功能
class weaponer:public actor{
public:
    weaponer(actor*_person);
    ~weaponer();

protected:
    //装饰器当中包含了一个角色
    actor*person;
};


//将武器的共性写入抽象装饰器内
weaponer::weaponer(actor*_person):person(_person){
    printf("装备武器\n");
}

  然后在这个抽象的装饰器类的基础上,派生出具体的装饰器类,也就是具体的武器,以实现不同的攻击效果。

class swordsman :public weaponer{
public:
    swordsman(actor*_person);
    void stab();

};


class wandsman:public weaponer{
public:
    wandsman(actor*_person);
    void fireball();
};


class javelinSoldier :public weaponer{
public:
    javelinSoldier(actor*_person);
    void cast();
};


swordsman::swordsman(actor*_person) :weaponer(_person){
    printf("装备武器为宝剑\n");
}

wandsman::wandsman(actor*_person):weaponer(_person){
    printf("装备武器为魔杖\n");
}
javelinSoldier::javelinSoldier(actor*_person): weaponer(_person){
    printf("装备武器为标枪\n");
}

void swordsman::stab(){
    this->person->attack();
    printf("发动刺击\n");
}


void wandsman::fireball(){
    this->person->attack();
    printf("发动火球攻击\n");
}

void javelinSoldier::cast(){
    this->person->attack();
    printf("发动投掷\n");
}

  经过装饰者模式的设计,我们可以让不同的职业装备不同的武器,实现不同的攻击效果。

 1 #include"Decorator.h"
 2 #include<stdlib.h>
 3 
 4 int main(){
 5 
 6     //战士
 7     warrior*a = new warrior();
 8     
 9 
10     // 现在为这个战士装备一把宝剑
11     swordsman*s = new swordsman(a);
12 
13     // 使用宝剑发动刺击打
14     s->stab();
15 
16     // 然后再为这个战士装备魔杖
17     wandsman*w = new wandsman(a);
18 
19     // 使用魔杖发动火球攻击
20     w->fireball();
21 
22     // 最后为战士装备标枪
23     javelinSoldier*j = new javelinSoldier(a);
24 
25     //投掷标枪
26     j->cast();
27     printf("******************************************\n");
28     //法师
29     wizard*b = new wizard();
30 
31     // 现在为这个法师装备一把宝剑
32     swordsman*s2 = new swordsman(b);
33 
34     // 使用宝剑发动刺击打
35     s2->stab();
36 
37     // 然后再为这个法师装备魔杖
38     wandsman*w2 = new wandsman(b);
39 
40     // 使用魔杖发动火球攻击
41     w2->fireball();
42 
43     // 最后为法师装备标枪
44     javelinSoldier*j2 = new javelinSoldier(b);
45     //投掷标枪
46     j2->cast();
47 
48     system("pause");
49     return 0;
50 }

运行结果:

 

 

 

装饰者模式的缺点

  任何设计模式都不是完美无缺的,装饰者模式也有缺陷,它的缺点和它的优点是一体的。为了增强功能的扩展性,装饰者模式将大量功能放入装饰器当中实现,每个装饰器实现的功能也不多,这可能会导致类数量的爆炸。在选择使用装饰者模式时,也应该考虑这个问题。