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


    写在前面

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

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

    1 class VisualObject {
    2 public:    
    3     virtual void show() = 0;    
    4     // ... other ...
    5 }

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

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

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

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

    要点梳理

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

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

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

     1 class ObjectDecorator : public VisualObject {
     2 public:
     3     ObjectDecorator(VisualObject *);
     4     
     5     virtual void show();
     6     // ... other ...
     7 private:
     8     VisualObject* _component;
     9 }
    10 
    11 void ObjectDecorator::show() {
    12     _component->show();
    13 }

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

     1 class MagicDecorator : public ObjectDecorator {
     2 public:
     3     MagicDecorator(VisualObject *, int);
     4     virtual void show();
     5     // ... other ...
     6 private:
     7     int _magicType;    
     8     
     9     void addMagic(int);
    10 }
    11 
    12 void MagicDecorator::show() {
    13     ObjectDecorator::show();
    14     addMagic(_magicType);
    15 }

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

    1 void World::add2Me(VisualObject* obj) {
    2     // ...
    3     obj->show();
    4     // ...
    5 }

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

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

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

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

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

    特点总结

    可以看到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中。

    写在最后

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

  • 相关阅读:
    【leetcode】面试题 01.04. 回文排列
    【leetcode】将数组分成和相等的三个部分
    【leetcode】杨辉三角
    【leetcode】杨辉三角 II
    【leetcode】判断路径是否相交
    【leetcode】路径总和
    【leetcode】山脉数组的峰顶索引
    053-4
    053-151
    053-272
  • 原文地址:https://www.cnblogs.com/xieziyu/p/3592285.html
Copyright © 2020-2023  润新知