[学习笔记]设计模式之Decorator

时间:2021-08-07 16:38:56

写在前面

为方便读者,本文已添加至索引

Decorator(装饰)模式,可以动态地给一个对象添加一些额外的职能。为了更好地理解这个模式,我们将时间线拉回Bridge模式笔记的结尾。那时,白雪公主射出了充满魔法力量的一箭。如好莱坞大片一般,那支飞出的箭矢散发出各种你能想到的美丽光芒。当然,我不会告诉你那支箭华华丽丽地射偏了。因为我们这次更关心这绚烂的魔法效果。要说的是,时の魔导士在建立这个平行世界的时候,定义了一个所有可见物体的抽象类VisualObject

 class VisualObject {
public:
virtual void show() = ;
// ... other ...
}

只要是出现在这个世界中,并能被世界之外的人们(比如说我们)看到的物体,都是它的子类。同时,必须实现show()方法。show定义了这个物体呈现给观众的样式。当然我们可以看到时の魔导士在创建箭矢时是如何偷懒的:

 class Arrow : public VisualObject {
public:
virtual void show() { cout << "你想它是什么样就是什么样。" << endl; }
// ... other ...
}

当然,这是不可行的。因为肯定有人会把Arrow想像成一个巨大的炮弹,甚至一头会飞的猪,以此来增强整个故事的动画性。但无论如何,当魔导士想要告诉大家,公主射出的箭矢是附着着冰霜效果的话,他必须明确地描述这支箭矢到底长什么样。如果偷懒到底,他大可以就创造个Arrow的子类IceArrow,只需多加个关于冰晶的描述就好了。

但是问题来了,既然可以附着魔法,箭矢就可以有更多更多的效果:附着火焰的,附着闪电的,长得更好看的,等等等等……如果每一个都用子类的话,必然会导致子类数目太多。况且,我们仅仅是希望给某个对象而不是整个类添加一些特效。同时别忘了我们还要考虑箭矢之外的可以附魔的东西。怎么办才好?时の魔导士采用了Decorator模式。

要点梳理

  • 目的分类
    • 对象结构型模式
  • 范围准则
    • 对象(该模式处理对象间的关系,这些关系在运行时刻是可以变化的,更具动态性)
  • 主要功能
    • 动态地给一个对象添加一些额外的职责。就增加功能来说, 它相比生成子类更为灵活。
  • 适用情况
    • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责
    • 处理那些可以撤消的职责
    • 当不能采用生成子类的方法进行扩充时。例如,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长
  • 参与部分
    • Component:定义一个对象接口,可以给这些对象动态地添加职责
    • ConcreteComponent:定义一个对象,可以给这个对象添加一些职责
    • Decorator:维持一个指向Component对象的指针,并定义一个与Component接口一致的接口
    • ConcreteDecorator:向组件添加职能
  • 协作过程
    • Decorator将请求转发给它的Component对象,并有可能在转发请求前后执行一些附加的动作
  • UML图

[学习笔记]设计模式之Decorator

示例分析 - 宛如大片中的魔法箭矢

[学习笔记]设计模式之Decorator

请不要太在意上面这幅图到底是谁。为了能给箭矢(乃至更多物体)增加魔法效果,魔导士引入了ObjectDecorator类:

 class ObjectDecorator : public VisualObject {
public:
ObjectDecorator(VisualObject *); virtual void show();
// ... other ...
private:
VisualObject* _component;
} void ObjectDecorator::show() {
_component->show();
}

ObjectDecorator装饰由_component实例变量引用的VisualObject,这个变量在构造器中被初始化。同时,ObjectDecorator将show的请求转发给_component。我们将看到ObjectDecorator的子类之一MagicDecorator,它将能给可视化物件添加魔法的神奇效果:

 class MagicDecorator : public ObjectDecorator {
public:
MagicDecorator(VisualObject *, int);
virtual void show();
// ... other ...
private:
int _magicType; void addMagic(int);
} void MagicDecorator::show() {
ObjectDecorator::show();
addMagic(_magicType);
}

类似的,我们也可以实现别的装饰类。我们接下来要把这个箭矢放到这个世界中去。假设World类(一个单例类)已经为此提供了一个add2Me操作:

 void World::add2Me(VisualObject* obj) {
// ...
obj->show();
// ...
}

现在我们要让身处世界之外的人也看到一根箭矢,只需这么做:

 Arrow* arrow = new Arrow();
World::getInstance()->add2Me(arrow);

那么此刻,正是展现电影特效各种神奇绚丽效果的时刻了。我们在将箭矢放入世界之前,先给它加上一层寒冰魔法的装饰:

 #define ICEMAGIC 1
World::getInstance()->add2Me(new MagicDecorator(arrow, ICEMAGIC));

OK,白雪公主射出了一支带有寒冰魔法的箭矢。下面的UML图说明了我们这个例子:

[学习笔记]设计模式之Decorator

特点总结

可以看到Decorator装饰模式有以下2个主要优点和缺点:

  1. 比静态继承更灵活。它提供了更加灵活的向对象添加职能的方式,可以用添加和分离的方法,用装饰在运行时刻增加和删除职能。同时使用Decorator模式可以很容易地重复添加一个特性,比如让箭矢附上冰与火双重魔法。
  2. 避免在层次结构高层的类有太多的特征。它并不试图在一个复杂的可定制的类中支持所有可预见的特征,相反,我们可以定义一个简单的类,并且用Decorator类给它逐渐地添加功能。
  3. Decorator与它的Component不一样。如果我们从对象标识的观点出发,一个被装饰了的组件与这个组件是有差别的,因此,使用装饰时不应该依赖对象标识。
  4. 有许多小对象。采用Decorator模式进行系统设计往往会产生许多看上去类似的小对象,这些对象仅仅在他们相互连接的方式上有所不同,而不是它们的类或是它们的属性值有所不同。尽管对于那些了解这些系统的人来说,很容易对它们进行定制,但是很难学习这些系统,排错也很困难。

同样,我们在使用这个方法时,也要注意:

  1. 接口的一致性。装饰对象的接口必须与它所装饰的Component的接口是一致的,因此,所有的ConcreteDecorator类必须有一个公共的父类(至少在C++中如此)。
  2. 保持Component类的简单性。为了保证接口的一致性,组件和装饰必须有一个公共的Component父类,因此保持这个类的简单性是很重要的。它应集中于定义接口而不是存储数据。否则Component类会变得过于复杂和庞大,因而难以大量使用
  3. 省略抽象的Decorator类。当我们仅需要添加一个职责时,没有必要定义抽象Decorator类。我们常常需要处理现存的类层次结构而不是设计一个新系统,这时我们可以把Decorator向Component转发请求的职责合并到ConcreteDecorator中。

写在最后

今天的笔记就到这里了,欢迎大家批评指正!如果觉得可以的话,好文推荐一下,我会非常感谢的!