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