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

写在前面

最近时间比较紧,所以发文的速度相对较慢了。但是看到园子里有很多朋友对设计模式感兴趣,我感觉很高兴,能够和大家一起学习这些知识。

之前的文章中,我们已经介绍了对象创建型对象结构型的设计模式(请参见索引)。从本篇开始,我们将接触的是对象行为型设计模式。所谓行为模式涉及到算法和对象间职责的分配。在对象和类的模式之外,还涉及了它们之间的通信模式。比如我们今次的主题:Chain of Responsibility(职责链)模式,它就描述了一种请求的传递和响应机制。

为了能直观地理解职责链模式,我们将继续采用要点梳理和示例讲解的方式进行。首先来回顾下该系列笔记的主要人物设定(参见Abstract Factory笔记):七个小霍比特人和白雪公主。耳熟能详的故事背景我就不多说了,白雪公主是逃到森林里的。森林里的生活,对于白雪公主而言是艰辛的,她总是得依靠小霍比特人的帮助和照料。七位Hobbits各有所长,譬如会做美味食物的theCook,睿智博学的theWise(参见Adapter模式笔记),勇敢善战的theWarrior(参见Factory Method模式笔记)等等。

这和我们的职责链有何关系?当我们可爱的白雪公主需要小霍比特人帮助的时候,她可能并不知道谁最终能帮到她。职责链模式正好提供了一种对应的解决方式。它通过给多个对象处理一个请求的机会,从而将提交请求的对象与可能提供解决方案的对象解耦。让我们举个简单的例子,在森林小屋里白雪公主睡得不踏实,她的床太小了,她想要大一点的床。于是她找到了theWise,向他提出了这个请求。theWise接收了请求,但是他处理不了。所以他接着把公主的请求转达给theWarrior。但theWarrior还是解决不了,所以他又找来了theWoodCutter(参见Bridge模式笔记)。于是theWoodCutter跑到森林里砍了一大堆木头回来,做了一张很大的床给公主睡。

在上面所说的例子中,每个小霍比特人都能够接收公主的请求,并且每个人都有一个最亲密的伙伴(后继者)。如果他发现自己处理不了这个请求时,他只需简单地把请求转达给亲密的小伙伴(后继者)即可。可以认为,公主的请求在一条霍比特人的关系链上进行传递,直至其中一个对象把它处理掉。从链中第一个对象开始,收到的请求要么亲自处理它,要么转发给链中的下一个候选者。提交请求的对象并不明确地知道哪一个对象将会处理它,我们称该请求有一个隐式的接收者(implicit receiver)

为了进一步理解职责链模式,我们进行一些要点梳理。

要点梳理

  • 目的分类

    • 对象行为型模式
  • 范围准则
    • 对象(该模式处理对象间的关系,这些关系在运行时刻是可以变化的,更具动态性)
  • 主要功能
    • 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
  • 适用情况
    • 有多个的对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定。
    • 我们想在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
    • 可处理一个请求的对象集合应被动态指定。
  • 参与部分
    • Handler:定义一个处理请求的接口;(可选)实现后继链
    • ConcreteHandler:处理它所负责的请求;可访问它的后继者;如果可处理该请求,就处理之;否则将该请求转发给它的后继者
    • Client:向链上的ConcreteHandler对象提交请求
  • 协作过程
    • 当客户提交一个请求时,请求沿链传递直至有一个ConcreteHandler对象负责处理它。
  • UML图

一种典型的对象结构

示例分析 - 有困难找我们

了解了基本概念之后,让我们回到前言中所讲的例子。下面将用C++代码来模拟实现其中的职责链。首先,Hobbit类是小霍比特人们的基类:

 #define ABILITY_COOK 1
#define ABILITY_FIGHT 2
#define ABILITY_CARP 3
#define ABILITY_KNOWLEDGE 4
// ... other ability types ... class Hobbit {
public:
Hobbit(Hobbit* h = 0, int a = 0); virtual bool hasAbility(int);
virtual void handleRequest(int);
private:
Hobbit* _successor; // the intimate friend.
int _ability;
};

它定义了处理请求的接口handleRequest,同时也保持了对象链中它的后继者的引用_successor(也就是前文所指的最亲密的小伙伴)。它还维护一个_ability,代表这个小霍比特人的最擅长能力。handleRequest是最关键的操作,它可以被子类重定义,这里默认是传递给后继者去处理。hasAbility则是一个辅助操作,用于判断对象是否具备相应的能力。

 Hobbit::Hobbit(Hobbit* h, int a): _successor(h), _ability(a) {}

 bool Hobbit::hasAbility(int a) {
return _ability == a;
} void Hobbit::handleRequest() {
if (_successor !=) {
_successor->handleRequest();
}
}

theWarrior是HobbitWarrior类的实例对象,而HobbitWarrior则是Hobbit的子类:

 class HobbitWarrior : public Hobbit {
protected:
HobbitWarrior(Hobbit* h, int a = ABILITY_FIGHT); virtual void handleRequest(int);
} HobbitWarrior::HobbitWarrior(Hobbit* h, int a):Hobbit(h, a) {} HobbitWarrior::handleRequest(int a) {
if (hasAbility(a)) {
// handle this request.
} else {
Hobbit::handleRequest(a);
}
}

在这里,HobbitWarrior版本的handleRequest首先会检测自己是否具备该能力,如果没有的话,它将把这个请求转发给后继者。同理,我们有非常类似的HobbitWoodCutter类:

 class HobbitWoodCutter : public Hobbit {
protected:
HobbitWoodCutter(Hobbit* h, int a = ABILITY_CARP); virtual void handleRequest(int);
} HobbitWoodCutter::HobbitWoodCutter(Hobbit* h, int a):Hobbit(h, a) {} HobbitWoodCutter::handleRequest(int a) {
if (hasAbility(a)) {
// handle this request.
} else {
Hobbit::handleRequest(a);
}
}

同理还有HobbitWise类等等,下面我们就创建并连接这些对象以演示前言部分的例子:

 HobbitWoodCutter* theWoodCutter = new HobbitWoodCutter();
HobbitWarrior* theWarrior = new HobbitWarrior(theWoodCutter);
HobbitWise* theWise = new HobbitWise(theWarrior);

然后白雪公主找到theWise,向他请求一个木工活:

theWise->handleRequest(ABILITY_CARP);

后面发生的事,大家就都知道了。对应于这个例子的UML图如下:

特点总结

从例子我们可以看出,白雪公主无需知道小霍比特人之间的联系,她只需要知道,她的请求会得到应有的响应(除了完成请求之外,当然也有可能大家都完不成这个请求)。

用专业的术语表示,职责链模式具有如下优缺点:

  1. 降低耦合度。该模式使得一个对象无需知道是其他哪一个对象处理其请求。接收者和发送者都没有对方的明确的信息,且链中的对象不需知道链的结构。这样一来,职责链简化了对象的相互连接。它们仅需保持一个指向其后继者的引用,而不需保持它所有的候选接受者的引用。
  2. 增强了给对象指派职责Responsibility的灵活性。我们可以通过在运行时刻对该链进行动态的增加或修改来增加或改变处理一个请求的那些职责,这给我们更大的灵活性。
  3. 缺点就是不保证被处理。既然一个请求没有明确的接收者,那么就不能保证它一定会被处理,该请求可能一直到链的末端都得不到处理。一个请求也可能因该链没有被正确配置而得不到处理。

职责链模式是一个看起来简单,但是却容易被误解的模式。最常见的误区就是上下级关系这块。事实上职责链并没有严格的上下级关系,只是保持一个对后继者的引用而已。后继者可以是它父类的对象,也可以是同级的对象。

写在最后

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    为方便读者,本文已添加至索引: 设计模式 学习笔记索引 写在前面 Flyweight(享元)模式运用共享技术,可以有效地支持大量细粒度的对象.今天我们会去参观小霍比特人们的酿酒工坊……等等,不是享元模 ...

随机推荐

  1. bzoj 3626 [LNOI2014]LCA(离线处理+树链剖分,线段树)

    3626: [LNOI2014]LCA Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 1272  Solved: 451[Submit][Status ...

  2. 使用alloctor模板来实现string类

    虽然以前做过更复杂的各种数据结构,不过那只是在看完c++prime7章后做的,没有考虑到类的拷贝体现出来是类值还是类指针,于是写了一些半成品类,不过那些主要是练数据结构,不想再改,于是就想办法模仿了下 ...

  3. 一段网上java常见escape和unescape方法的BUG

    escape编码和unescape编码,就是将一个字符转换为16进制unicode编码,前面加%字符进行标识. 此处不再多做解释,参考这里:http://www.jb51.net/article/23 ...

  4. JAVA--线程wait()、lnotify()和notifyAll()方法

    join()方法是Thread类的一个方法,而wait().notify().notifyAll()是java.lang.Object类的方法,这意味着,任何一个Java对象(包括线程对象)都有wai ...

  5. poj 2503 字符串hash

    题目链接:http://poj.org/problem?id=2503 代码: #include<cstdio> #include<cstring> #include<i ...

  6. iOS开发笔记之TableView优化

    TableView相信只要是做iOS开发的就不会陌生,目前大多数iOS的app都是采用TabBar+NavigationBar+TableViewController这一主流框架, 既然用的这么频繁, ...

  7. PHP开发APP接口(二)

    这里将会调用前面博客的数据库连接单例.文件缓存类和开发APP接口(一) <?php // http://app.com/list.php?page-=1&pagesize=12 requ ...

  8. 阅读STL源码剖析之list

    首先,以我之愚见,觉得有两个地方可以优化一下,不知对否,有待商榷: 1.在list的结点定义中 template<typename T> struct __list_node { type ...

  9. jetty之安装,配置,部署,运行

    上篇文章中详解了关于什么是jetty,后续文章主要是介绍jetty的使用.本章介绍jetty环境的配置及部署war包. 1. 安装 1. 先下载一个jetty的压缩包,下载地址:http://www. ...

  10. (linux shell)第一章--小试牛刀(上)

    来源:(linux shell)第一章--小试牛刀(上) 从今天開始,我们一起来学习<linux shell脚本攻略>这本书. 1.1简单介绍 shell脚本一般是一个以#!起始的文本文件 ...