写在前面

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

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图

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

请不要太在意上面这幅图到底是谁。为了能给箭矢(乃至更多物体)增加魔法效果,魔导士引入了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装饰模式有以下2个主要优点和缺点:

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

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

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

写在最后

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

[学习笔记]设计模式之Decorator的更多相关文章

  1. [学习笔记]设计模式之Proxy

    为方便读者,本文已添加至索引: 设计模式 学习笔记索引 写在前面 “魔镜啊魔镜,谁是这个世界上最美丽的人?” 每到晚上,女王都会问魔镜相同的问题(见Decorator模式).这是她还曾身为女巫时留下的 ...

  2. [学习笔记]设计模式之Abstract Factory

    写在前面 为方便读者,本文已添加至索引: 设计模式 学习笔记索引 在上篇笔记Builder设计模式中,时の魔导士祭出了自己的WorldCreator.尽管它因此能创造出一个有山有树有房子的世界,但是白 ...

  3. [学习笔记]设计模式之Builder

    写在前面 为方便读者,本文已添加至索引: 设计模式 学习笔记索引 作为一个新入职的魔导士呢,哦不,是程序员,我以为并没有太多机会去设计项目的软件架构.但是,工作一段时间之后,自己渐渐意识到,哪怕是自己 ...

  4. [学习笔记]设计模式之Adapter

    写在前面 为方便读者,本文已添加至索引: 设计模式 学习笔记索引 Adapter(适配器)模式主要解决接口不匹配的问题.为此,让我们要回到最初Builder模式创建平行世界时,白雪公主和小霍比特人的谜 ...

  5. [学习笔记]设计模式之Bridge

    写在前面 为方便读者,本文已添加至索引: 设计模式 学习笔记索引 “魔镜啊魔镜,谁是这个世界上最美丽的人?”月光中,一个低沉的声音回荡在女王的卧室.“是美丽的白雪公主,她正和小霍比特人们幸福快乐地生活 ...

  6. [学习笔记]设计模式之Prototype

    写在前面 为方便读者,本文已添加至索引: 设计模式 学习笔记索引 在笔记Builder模式中,我们曾见到了最初用于创建平行世界的函数createWorld,并且它是Mage类的成员函数(毕竟是专属于魔 ...

  7. [学习笔记]设计模式之Command

    为方便读者,本文已添加至索引: 设计模式 学习笔记索引 写在前面 在上篇Chain of Responsibility(职责链)模式笔记中,我们学习了一种行为型设计模式.今天,我们继续这一主题,来学习 ...

  8. [学习笔记]设计模式之Chain of Responsibility

    为方便读者,本文已添加至索引: 设计模式 学习笔记索引 写在前面 最近时间比较紧,所以发文的速度相对较慢了.但是看到园子里有很多朋友对设计模式感兴趣,我感觉很高兴,能够和大家一起学习这些知识. 之前的 ...

  9. [学习笔记]设计模式之Composite

    为方便读者,本文已添加至索引: 设计模式 学习笔记索引 写在前面 在Composite(组合)模式中,用户可以使用多个简单的组件以形成较大的组件,而这些组件还可能进一步组合成更大的.它重要的特性是能够 ...

随机推荐

  1. map/reduce实现 排序

    import java.io.IOException; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.co ...

  2. PHP两个字符串比较(人为出错),两字符串类型和数据表面相等,但strcmp()结果不为0

    PHP中,比较两个字符串是否相等用:strcmp(): PHP strcmp() 函数 PHP String 函数 定义和用法 strcmp() 函数比较两个字符串. 该函数返回: 0 - 如果两个字 ...

  3. 3 weekend110的配置hadoop(格式化) + 一些问题解决 + 未免密码配置

    由于,之前,已经在/etc/profile里,配置了hadoop的全局变量,所以,现在可以在任何路径下执行hadoop命令. 来玩玩, 其实啊,在这里,出现了错误, 参考解决链接: http://it ...

  4. SQL查询记录添加序号(HANA)

    语法:ROW_NUMBER() OVER(PARTITION BY COLUMN ORDER BY COLUMN) row_number() OVER() 从1开始,为每一条分组记录返回一个数字,这里 ...

  5. PowerDesigner跟表的字段加注释

    选择那个表 右键- >Properties- >Columns- >Customize Columns and Filter(或直接用快捷键Ctrl+U)- >Comment( ...

  6. PAT---1005. Spell It Right (20)

    #include<iostream> #include<stack> #include<string.h> ]= {"zero", " ...

  7. 第一个Android项目——计算器

    第一个Android项目——计算器 效果 开始学Android一两个星期了,学习了一下基本的Activity.简单控件及几个简单布局,打算找个东西来练练手,于是就选择发计算器.关于计算器中用到的四则运 ...

  8. [Javascript] MetaProgramming: function name

    Each function should have a 'name' property. It can be anonymous, empty, the same as function name, ...

  9. android开发之国际化

    国际化,听起来高大上,做起来很简单. 我们来实现一个简单的效果,让应用根据系统的语言来做不同的显示,假如android系统默认是英语,应用就以英文的形式显示,如果android系统默认是中文,则应用就 ...

  10. vi 使用笔记

    基本A 当前行追加J 去除本行和下一行之间的换行符(写CSS利器)~ 光标所在处的字符进行大小写互换* 向前搜索目前光标所在的单词# 向后搜索目前光标所在的单词% 查找与光标所在处相匹配的反括号, 包 ...