相信大家都玩过类似于“斗地主”的纸牌游戏,某人出牌给他的下家,下家看看手中的牌,如果要不起,则将出牌请求转发给他的下家,其下家再进行判断。一个循环下来,如果其他人都要不起该牌,则最初的出牌者可以打出新牌。在这个过程中,纸牌作为一个请求沿着一条链在传递,每一位纸牌的玩家都可以处理该请求。在设计模式中,也有一种专门用于处理这种请求链式的模式,它就是职责链模式。

职责链模式(Chain of Responsibility) 学习难度:★★★☆☆ 使用频率:★★☆☆☆

一、采购单的分级审批模块设计

需求背景:M公司承接了某企业SCM(Supply Chain Management,供应链管理)系统的开发任务,其中包含一个采购审批子系统。该企业的采购审批是分级进行的,即根据采购金额的不同由不同层次的主管人员来审批:主任可以审批5万元以下(不包括5万)的采购单,副董事长可以审批5万~10万(不包括10万)的采购单,50万元以及以上的采购单就需要开董事会讨论决定,如下图所示:

  M公司开发人员提出了一个初始解决方案,提供了一个采购单处理类PurchaseRequestHandler用于统一处理采购单,其框架代码如下:

    /// <summary>
/// 采购单处理类
/// </summary>
public class PurchaseRequestHandler
{
// 递交采购单给审批者
public void SendRequestToApprover(PurchaseRequest request)
{
if (request.Amount < ) // 主任可审批该采购单
{
HandleByDirector(request);
}
else if(request.Amount < ) // 副董事长可审批该采购单
{
HandleByVicePresident(request);
}
else if (request.Amount < ) // 董事长可审批该采购单
{
HandleByPresident(request);
}
else
{
HandleByCongress(request); // 董事会可审批该采购单
}
} // 主管审批采购单
private void HandleByDirector(PurchaseRequest request)
{
// 代码省略
} // 副董事长审批采购单
private void HandleByVicePresident(PurchaseRequest request)
{
// 代码省略
} // 董事长审批采购单
private void HandleByPresident(PurchaseRequest request)
{
// 代码省略
} // 董事会审批采购单
private void HandleByCongress(PurchaseRequest request)
{
// 代码省略
}
}

  不过仔细分析后发现,上述方案存在以下3个问题:

  (1)PurchaseRequestHandler类较为庞大,各个级别的审批方法都集中在一个类中,违反了单一职责原则,测试和维护难度较大。

  (2)如果需要新增一个新的审批级别或调整任何一级的审批金额和审批细节时都必须修改源代码并进行严格测试。此外,如果需要移除某一级别时也需要对源代码进行修改,违反了开闭原则。

  (3)审批流程的设置缺乏灵活性,现在的审批流程是“主任->副董事长->董事长->董事会”,如果需要改为“主任->董事长->董事会”,在此方案中只能通过修改源代码来实现,客户端无法定制审批流程。

  那么如何破呢?别急,来看看职责链模式。

二、职责链模式概述

2.1 职责链模式简介

职责链(Chain of Responsibility)模式:避免将请求发送者与接受者耦合在一起,让多个对象都有机会接受请求,将这些对象连成一条链,并且沿着这条链传递请求,直到有对象处理它为止。职责链模式是一种对象行为型模式。  

2.2 职责链模式结构

  职责链模式结构的核心就在于引入了一个抽象处理者,其结构如下图所示:

  在职责链模式结构图中包含以下两个角色:

  (1)Handler(抽象处理者):定义了一个处理请求的接口,一般设计为抽象类,由于不同的具体处理者处理请求的方式不同,因此在其中定义了抽象请求处理方法。

  (2)ConcreteHandler(具体处理者):它是抽象处理者的子类,可以处理用户请求,它实现了在抽象处理者中定义的抽象请求处理方法。在处理请求之前需要判断是否有相应的处理权限,如果可以则处理,否则则将请求转发给后继者。

三、重构采购单分级审批模块

3.1 重构后的设计

  其中,抽象类Approver充当抽象处理类,Director, VicePresident, President以及Congress 充当具体处理者,PurchaseRequest充当请求类。

3.2 具体代码实现

  (1)请求类:PurchaseRequest

    /// <summary>
/// 采购单:请求类
/// </summary>
public class PurchaseRequest
{
// 采购金额
public double Amount { get; set; }
// 采购单编号
public string Number { get; set; }
// 采购目的
public string Purpose { get; set; } public PurchaseRequest(double amount, string number, string purpose)
{
Amount = amount;
Number = number;
Purpose = purpose;
}
}

  (2)抽象处理者:Approver

    /// <summary>
/// 审批者类:抽象处理者
/// </summary>
public abstract class Approver
{
protected Approver successor; // 定义后继对象
protected string name; // 审批者姓名 public Approver(string name)
{
this.name = name;
} // 设置后继者
public void SetSuccessor(Approver successor)
{
this.successor = successor;
} // 抽象请求处理方法
public abstract void ProcessRequest(PurchaseRequest request);
}

  (3)具体处理者:Director, VicePresident, President以及Congress

    /// <summary>
/// 总监:具体处理类
/// </summary>
public class Director : Approver
{
public Director(string name) : base(name)
{
} // 具体请求处理方法
public override void ProcessRequest(PurchaseRequest request)
{
if (request.Amount < )
{
// 处理请求
Console.WriteLine("主管 {0} 审批采购单:{1},金额:{2} 元,采购目的:{3}。",
this.name, request.Number, request.Amount, request.Purpose);
}
else
{
// 如果处理不了,转发请求给更高层领导
this.successor.ProcessRequest(request);
}
}
} /// <summary>
/// 副总裁:具体处理类
/// </summary>
public class VicePresident : Approver
{
public VicePresident(string name) : base(name)
{
} // 具体请求处理方法
public override void ProcessRequest(PurchaseRequest request)
{
if (request.Amount < )
{
// 处理请求
Console.WriteLine("副总裁 {0} 审批采购单:{1},金额:{2} 元,采购目的:{3}。",
this.name, request.Number, request.Amount, request.Purpose);
}
else
{
// 如果处理不了,转发请求给更高层领导
this.successor.ProcessRequest(request);
}
}
} /// <summary>
/// 总裁:具体处理者
/// </summary>
public class President : Approver
{
public President(string name) : base(name)
{
} // 具体请求处理方法
public override void ProcessRequest(PurchaseRequest request)
{
if (request.Amount < )
{
// 处理请求
Console.WriteLine("总裁 {0} 审批采购单:{1},金额:{2} 元,采购目的:{3}。",
this.name, request.Number, request.Amount, request.Purpose);
}
else
{
// 如果处理不了,转发请求给更高层领导
this.successor.ProcessRequest(request);
}
}
} /// <summary>
/// 董事会:具体处理者
/// </summary>
public class Congress : Approver
{
public Congress(string name) : base(name)
{
} // 具体请求处理方法
public override void ProcessRequest(PurchaseRequest request)
{
// 处理请求
Console.WriteLine("董事会 {0} 审批采购单:{1},金额:{2} 元,采购目的:{3}。",
this.name, request.Number, request.Amount, request.Purpose);
}
}

  (4)客户端测试:

    public class Program
{
public static void Main(string[] args)
{
// 创建职责链
Approver andy = new Director("Andy");
Approver jacky = new VicePresident("Jacky");
Approver ashin = new President("Ashin");
Approver meeting = new Congress("Congress"); andy.SetSuccessor(jacky);
jacky.SetSuccessor(ashin);
ashin.SetSuccessor(meeting);
// 构造采购请求单并发送审批请求
PurchaseRequest request1 = new PurchaseRequest(45000.00,
"MANULIFE201706001",
"购买PC和显示器");
andy.ProcessRequest(request1); PurchaseRequest request2 = new PurchaseRequest(60000.00,
"MANULIFE201706002",
"2017开发团队活动");
andy.ProcessRequest(request2); PurchaseRequest request3 = new PurchaseRequest(160000.00,
"MANULIFE201706003",
"2017公司年度旅游");
andy.ProcessRequest(request3); PurchaseRequest request4 = new PurchaseRequest(800000.00,
"MANULIFE201706004",
"租用新临时办公楼");
andy.ProcessRequest(request4); Console.ReadKey();
}
}

  编译运行后的结果如下图所示:

  

3.3 需求扩展实现

  这时,假设需要在系统中新增一个新的具体处理者,例如增加一个经理(Manager)角色可以审批5万~8万(不包括8万)的采购单。因此,我们可以新增一个具体处理者:Manager

    /// <summary>
/// 经理:具体处理者
/// </summary>
public class Manager : Approver
{
public Manager(string name) : base(name)
{
} // 具体请求处理方法
public override void ProcessRequest(PurchaseRequest request)
{
if (request.Amount < )
{
// 处理请求
Console.WriteLine("经理 {0} 审批采购单:{1},金额:{2} 元,采购目的:{3}。",
this.name, request.Number, request.Amount, request.Purpose);
}
else
{
this.successor.ProcessRequest(request);
}
}
}

  由于链的创建过程由客户端负责,因此此扩展对原有类库无任何影响,符合开闭原则。而我们需要做的,仅仅是在客户端代码中新增职责链关系的创建即可。

    public class Program
{
public static void Main(string[] args)
{
// 创建职责链
Approver andy = new Director("Andy");
Approver jacky = new Manager("Jacky");
Approver ashin = new VicePresident("Ashin");
Approver anya = new President("Anya");
Approver meeting = new Congress("Congress"); andy.SetSuccessor(jacky);
jacky.SetSuccessor(ashin);
ashin.SetSuccessor(anya);
anya.SetSuccessor(meeting);
// 构造采购请求单并发送审批请求
PurchaseRequest request1 = new PurchaseRequest(45000.00,
"MANULIFE201706001",
"购买PC和显示器");
andy.ProcessRequest(request1); PurchaseRequest request2 = new PurchaseRequest(60000.00,
"MANULIFE201706002",
"2017开发团队活动");
andy.ProcessRequest(request2); PurchaseRequest request3 = new PurchaseRequest(160000.00,
"MANULIFE201706003",
"2017公司年度旅游");
andy.ProcessRequest(request3); PurchaseRequest request4 = new PurchaseRequest(800000.00,
"MANULIFE201706004",
"租用新临时办公楼");
andy.ProcessRequest(request4); Console.ReadKey();
}
}

  重新编译运行后的结果如下图所示:

  

四、职责链模式总结

4.1 主要优点

  (1)使得一个对象无需知道是其他哪一个对象处理其请求,对象仅需知道该请求会被处理即可,且链式结构由客户端创建 => 降低了系统的耦合度

  (2)在系统中增加一个新的具体处理者无须修改原有系统源代码,只需要在客户端重新建立链式结构即可 => 符合开闭原则

4.2 主要缺点

  (1)由于一个请求没有一个明确地接受者 => 无法保证它一定会被处理

  (2)对于较长的职责链 => 系统性能有一定影响且不利于调试

  (3)如果建立链不当,可能会造成循环调用 => 导致系统进入死循环

4.3 应用场景

  (1)有多个对象处理同一个请求且无需关心请求的处理对象时谁以及它是如何处理的 => 比如各种审批流程

  (2)可以动态地指定一组对象处理请求,客户端可以动态创建职责链来处理请求,还可以改变链中处理者之间的先后次序 => 比如各种流程定制

参考资料

  

  刘伟,《设计模式的艺术—软件开发人员内功修炼之道》

作者:周旭龙

出处:http://edisonchou.cnblogs.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

设计模式的征途—14.职责链(Chain of Responsibility)模式的更多相关文章

  1. 设计模式C++描述----05.职责链(Chain of Responsibility)模式

    一. 概述 职责链模式: 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系.将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止. 二. 举个例子 员工要求加薪 ...

  2. 职责链(Chain of Responsibility)模式在航空货运中的运用实例

    设计模式这东西,基本上属于“看懂一瞬间,用会好几年”.只有实际开发中,当某一模式很好的满足了业务需求时,才会有真切的感觉.借用一句<闪电侠>中,绿箭侠教导闪电侠的台词:“不是你碰巧遇到了它 ...

  3. 设计模式(十三) 职责链(chain of responsibility)

    软件领域中的设计模式为开发人员提供了一种使用专家设计经验的有效途径.设计模式中运用了面向对象编程语言的重要特性:封装.继承.多态,真正领悟设计模式的精髓是可能一个漫长的过程,需要大量实践经验的积累.最 ...

  4. C++设计模式实现--职责链(Chain of Responsibility)模式

    一. 概述 职责链模式: 使多个对象都有机会处理请求.从而避免请求的发送者和接收者之间的耦合关系.将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止. 二. 举个样例 员工要求加薪 ...

  5. atitit.(设计模式1)--—职责链(chain of responsibility)最佳实践O7 转换日期

    atitit.设计模式(1)---职责链模式(chain of responsibility)最佳实践O7 日期转换 1. 需求:::日期转换 1 2. 能够选择的模式: 表格模式,责任链模式 1 3 ...

  6. atitit.设计模式(1)--—职责链模式(chain of responsibility)最佳实践O7 日期转换

    atitit.设计模式(1)---职责链模式(chain of responsibility)最佳实践O7 日期转换 1. 需求:::日期转换 1 2. 可以选择的模式: 表格模式,责任链模式 1 3 ...

  7. Java设计模式(14)责任链模式(Chain of Responsibility模式)

    Chain of Responsibility定义:Chain of Responsibility(CoR) 是用一系列类(classes)试图处理一个请求request,这些类之间是一个松散的耦合, ...

  8. C#设计模式之二十一职责链模式(Chain of Responsibility Pattern)【行为型】

    一.引言   今天我们开始讲"行为型"设计模式的第八个模式,该模式是[职责链模式],英文名称是:Chain of Responsibility Pattern.让我们看看现实生活中 ...

  9. C#设计模式之二十职责链模式(Chain of Responsibility Pattern)【行为型】

    一.引言 今天我们开始讲“行为型”设计模式的第八个模式,该模式是[职责链模式],英文名称是:Chain of Responsibility Pattern.让我们看看现实生活中的例子吧,理解起来可能更 ...

随机推荐

  1. 提升单元测试体验的利器--Mockito使用总结

    为神马要使用Mockito? 在编写单元测试的时候,为了尽可能的保证隔离性,我们时常需要对某些不容易构造或者不容易获取或者对外部环境有依赖的对象,用一个虚拟的对象来创建以便于测试.假设你正在开发的的代 ...

  2. gulp报错

    这个问题网上搜索到的答案不一定能够解决问题,有可能是node版本问题,需要升级 到最新版就可以了....

  3. javacpp-opencv图像处理之1:实时视频添加文字水印并截取视频图像保存成图片,实现文字水印的字体、位置、大小、粗度、翻转、平滑等操作

    欢迎大家积极开心的加入讨论群 群号:371249677 (点击这里进群) javaCV图像处理系列: javaCV图像处理之1:实时视频添加文字水印并截取视频图像保存成图片,实现文字水印的字体.位置. ...

  4. JMS 之 Active MQ 消息存储

    一.消息的存储方式 ActiveMQ支持JMS规范中的持久化消息与非持久化消息 持久化消息通常用于不管是否消费者在线,它们都会保证消息会被消费者消费.当消息被确认消费后,会从存储中删除 非持久化消息通 ...

  5. Android 创建虚拟机时“提示no system images installed for this target”

    经上网查证,发现原因在于CPU/ABI选项无法选择,并显示“No system images installed for this target”,也就是没有适合的系统镜像,通过与安装好了的ADT-b ...

  6. document事件及例子

    一.关于鼠标事件:onclick:鼠标单击触发 ondbclick:鼠标双击触发 onmouseover:鼠标移上触发 onmouseout:鼠标离开触发 onmousemove:鼠标移动触发 二.关 ...

  7. 网页中区分IE各版本

    CSS Hack 直接在CSS文件中写CSS Hack是非常直观的区分方法.区分不同IE版本的hack代码为 #content{ background:red; /* 所有浏览器 */ backgro ...

  8. Matlab: 路径的操作

    添加相对路径 在matlab中当代码很多时常常将结果存在不同的文件夹下面,常常使用相对路径对函数进行调用,但有时会存在问题.举个栗子: 代码结构如下: /codes/A/AA/code1.m /cod ...

  9. Linux常用操作命令(三)

    查看linux日志某几行 用逆序显示命令tail查看 命令格式:tail [  -r ] [  -n Number ] [ File ] [一]从第3000行开始,显示1000行.即显示3000~39 ...

  10. 【Android Developers Training】 43. 序言:管理音频播放

    注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...