职责链模式(Chain of Responsiblity),使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

从责任链模式的定义可以发现,责任链模式涉及的对象只有处理者角色,但由于有多个处理者,它们具有共同的处理请求的方法,所以这里抽象出一个抽象处理者角色进行代码复用。这样分析下来,责任链模式的结构图也就不言而喻了,具体结构图如下所示。

主要涉及两个角色:

  • 抽象处理者角色(Handler):定义出一个处理请求的接口。这个接口通常由接口或抽象类来实现。
  • 具体处理者角色(ConcreteHandler):具体处理者接受到请求后,可以选择将该请求处理掉,或者将请求传给下一个处理者。因此,每个具体处理者需要保存下一个处理者的引用,以便把请求传递下去。

在以下场景中可以考虑使用责任链模式:

  • 一个系统的审批需要多个对象才能完成处理的情况下,例如请假系统等。
  • 代码中存在多个if-else语句的情况下,此时可以考虑使用责任链模式来对代码进行重构。

C#职责链模式:

namespace 职责链模式
{
class Program
{
static void Main(string[] args)
{ CommonManager jinli = new CommonManager("金利");
Majordomo zongjian = new Majordomo("宗剑");
GeneralManager zhongjingli = new GeneralManager("钟精励");
jinli.SetSuperior(zongjian);
zongjian.SetSuperior(zhongjingli); Request request = new Request();
request.RequestType = "请假";
request.RequestContent = "小菜请假";
request.Number = ;
jinli.RequestApplications(request); Request request2 = new Request();
request2.RequestType = "请假";
request2.RequestContent = "小菜请假";
request2.Number = ;
jinli.RequestApplications(request2); Request request3 = new Request();
request3.RequestType = "加薪";
request3.RequestContent = "小菜请求加薪";
request3.Number = ;
jinli.RequestApplications(request3); Request request4 = new Request();
request4.RequestType = "加薪";
request4.RequestContent = "小菜请求加薪";
request4.Number = ;
jinli.RequestApplications(request4); Console.Read(); }
} //管理者
abstract class Manager
{
protected string name;
//管理者的上级
protected Manager superior; public Manager(string name)
{
this.name = name;
} //设置管理者的上级
public void SetSuperior(Manager superior)
{
this.superior = superior;
} //申请请求
abstract public void RequestApplications(Request request);
} //经理
class CommonManager : Manager
{
public CommonManager(string name)
: base(name)
{ }
public override void RequestApplications(Request request)
{ if (request.RequestType == "请假" && request.Number <= )
{
Console.WriteLine("{0}:{1} 数量{2} 被批准", name, request.RequestContent, request.Number);
}
else
{
if (superior != null)
superior.RequestApplications(request);
} }
} //总监
class Majordomo : Manager
{
public Majordomo(string name)
: base(name)
{ }
public override void RequestApplications(Request request)
{ if (request.RequestType == "请假" && request.Number <= )
{
Console.WriteLine("{0}:{1} 数量{2} 被批准", name, request.RequestContent, request.Number);
}
else
{
if (superior != null)
superior.RequestApplications(request);
} }
} //总经理
class GeneralManager : Manager
{
public GeneralManager(string name)
: base(name)
{ }
public override void RequestApplications(Request request)
{ if (request.RequestType == "请假")
{
Console.WriteLine("{0}:{1} 数量{2} 被批准", name, request.RequestContent, request.Number);
}
else if (request.RequestType == "加薪" && request.Number <= )
{
Console.WriteLine("{0}:{1} 数量{2} 被批准", name, request.RequestContent, request.Number);
}
else if (request.RequestType == "加薪" && request.Number > )
{
Console.WriteLine("{0}:{1} 数量{2} 再说吧", name, request.RequestContent, request.Number);
}
}
} //申请
class Request
{
//申请类别
private string requestType;
public string RequestType
{
get { return requestType; }
set { requestType = value; }
} //申请内容
private string requestContent;
public string RequestContent
{
get { return requestContent; }
set { requestContent = value; }
} //数量
private int number;
public int Number
{
get { return number; }
set { number = value; }
}
}
}

灵活可拆分的职责链节点

首先需要改写一下分别表示3种购买模式的节点函数,我们约定,如果某个节点不能处理请求,则返回一个特定的字符串'nextSuccessor'来表示该请求需要继续往后面传递:

var order500 = function(orderType,pay,stock){
if(orderType === 1 && pay === true){
console.log('500元定金预购,得到100优惠券');
}else{
return 'nextSucessor'; //我不知道下一个节点是谁,反正把请求往后面传递
}
}; var order200 = function(orderType,pay,stock){
if(orderType === 2 && pay === true){
console.log('200元定金预购,得到50优惠券');
}else{
return 'nextSucessor'; //我不知道下一个节点是谁,反正把请求往后面传递
}
}; var orderNormal = function(orderType,pay,stock){
if(stock > 0){
console.log('普通购买,无优惠券');
}else{
console.log('手机库存不足');
}
};

接下来需要把函数包装进职责链节点,我们定义一个构造函数Chain,在new Chain的时候传递参数即为需要被包装的函数,同时它拥有一个实例属性this.sucessor,表示在链中的下一个节点。

此外Chain的prototype中还有两个函数,它们的作用如下所示:

//Chain.prototype.setNextSucessor   指定在链中的下一个节点
//Chain.prototype.passRequest 传递请求给某个节点 var Chain = function(fn){
this.fn = fn;
this.successor = null;
}; Chain.prototype.setNextSuccessor = function(successor){
return this.successor = successor;
}; Chain.prototype.passRequest = function(){
var ret = this.fn.apply(this,arguments); if(ret === 'nextSuccessor'){
return this.successor && this.successor.passRequest.apply(this.successor,arguments);
} return ret;
};

现在我们把3个订单函数分别包装成职责链的节点:

var chainOrder500 = new Chain(order500);
var chainOrder200 = new Chain(order200);
var chainOrderNormal = new Chain(orderNormal);

然后指定节点在职责链中的顺序:

chainOrder500.setNextSuccessor(chainOrder200);
chainOrder200.setNextSuccessor(chainOrderNormal);

最后把请求传递给第一个节点:

chainOrder500.passRequest(1,true,500);    //输出:500元定金预购,得到100优惠券
chainOrder500.passRequest(2,true,500); //输出:200元定金预购,得到50优惠券
chainOrder500.passRequest(3,true,500); //输出:普通购买,无优惠券
chainOrder500.passRequest(1,false,0); //输出:手机库存不足

通过改进,我们可以自由灵活地增加、移除和修改链中的节点顺序,假如某天网站运营人员又想出了支持300元定金购买,那我们就在该链中增加一个节点即可:

var order300 = function(){
//具体实现略
}; chainOrder300 = new Chain(order300);
chainOrder500.setNextSuccessor(chainOrder300);
chainOrder300.setNextSuccessor(chainOrder200);

对于程序员来说,我们总是喜欢去改动那些相对容易改动的地方,就像改动框架的配置文件远比改动框架的源代码简单得多。在这里完全不用理会原来的订单函数代码,我们要做的只是增加一个节点,然后重新设置链中的相关节点的顺序。

异步的职责链

var fn1 = new Chain(function(){
console.log(1);
return 'nextSuccessor';
}); var fn2 = new Chain(function(){
console.log(2);
var self = this;
setTimeout(function(){
self.next();
},1000);
}); var fn3 = new Chain(function(){
console.log(3);
}); fn1.setNextSuccessor(fn2).setNextSuccessor(fn3);
fn1.passRequest();

职责链模式并非没有弊端,如果请求得不到答复,径直从链尾离开,或者抛出异常。在这种情况下,我们可以在链尾增加一个保底的接受者节点来处理这种即将离开链尾的请求。

另外,职责链模式使得程序中多了一些节点对象,可能在某一次的请求传递过程中,大部分节点并没有起到实质性的作用,它们的作用仅仅是让请求传递下去,从性能方面考虑,我们要避免长的职责链带来的性能损耗。

用AOP实现职责链
 

面我们改写一下Function.prototype.after函数,使得第一个函数返回'nextSuccessor'时,将请求继续传递给下一个函
数,无论是返回字符串'nextSuccessor'或者fase都只是一个约定,当然在这里我们也可以让函数返回false表示传递请求,选择
'nextSuccessor'字符串是因为它看起来更能表达我们的目的,代码如下:

Function.prototype.after = function(fn){
var self = this;
return function(){
var ret = self.apply(this,arguments);
if(ret === 'nextSuccessor'){
return fn.apply(this,arguments);
}
return ret;
}
}; var order = order500yuan.after(order200yuan).after(orderNormal); order(1,true,500); //输出:500元定金预购,得到100优惠券
order(2,true,500); //输出:200元定金预购,得到50优惠券
order(1,false,500); //输出:普通购买,无优惠券

用职责链模式获取文件上传对象

var getActiveUploadObj = function(){
try{
return new ActiveXObject("TXFTNActiveX.FTNUpload"); //IE上传控件
}catch(e){
return 'nextSuccessor';
}
}; var getFlashUploadObj = function(){
if(supportFlash()){
var str = '<object type="application/x-shockwave-flash"></object>';
return $(str).appendTo($('body'));
}
return 'nextSuccessor';
}; var getFormUploadObj = function(){
return $('<form><input name="file" type="file"/></form>').appendTo($('body'));
}; var getUploadObj = getActiveUploadObj.after(getFlashUploadObj).after(getFormUploadObj);
console.log(getUploadObj());

js职责链模式的更多相关文章

  1. Js 职责链模式 简单理解

    js 职责链模式 的简单理解.大叔的代码太高深了,不好理解. function Handler(s) { this.successor = s || null; this.handle = funct ...

  2. JS 职责链模式

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  3. js 职责链模式简要介绍

    定义: 使多个对象都有机会处理请求,避免发送者与接受者之间的耦合关系,将对象连成一条链,沿着这条链传递请求,直到有一个对象处理它. 如何把对象串联起来?解决方法通常是将另一个对象作为新创建对象的参数, ...

  4. JS常用的设计模式(15)—— 职责链模式

    职责链模式是一个对象A向另一个对象B发起请求,如果B不处理,可以把请求转给C,如果C不处理,又可以把请求转给D.一直到有一个对象愿意处理这个请求为止. 打个比方,客户让老板写个php程序.老板肯定不写 ...

  5. js设计模式(12)---职责链模式

    0.前言 老实讲,看设计模式真得很痛苦,一则阅读过的代码太少:二则从来或者从没意识到使用过这些东西.所以我采用了看书(<js设计模式>)和阅读博客(大叔.alloyteam.聂微东)相结合 ...

  6. JS设计模式(10)职责链模式(重要)

    什么是职责链模式? 重要性:4 星,在项目中能对 if-else 语句进行优化 定义:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到 ...

  7. js设计模式——6.模板方法模式与职责链模式

    js设计模式——6.模板方法模式与职责链模式 职责链模式

  8. 5.js模式-职责链模式

    1. 职责链模式 将对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止. var chain = function(fn){ this.fn = fn; this.successor = ...

  9. js原生设计模式——13桥接模式(相同业务逻辑抽象化处理的职责链模式)

    桥接模式之多元化类之间的实例化调用实例 <!DOCTYPE html><html lang="en"><head>    <meta ch ...

随机推荐

  1. Linux考试题附答案

    一.选择题 1.在登录Linux时,一个具有唯一进程ID号的shell将被调用,这个ID是什么(B)? A.NID B.PID C.UID D.CID 2.下面哪个目录存放用户密码信息(B) A./b ...

  2. 我的Android进阶之旅------>百度地图学习:BDLocation.getLocType ( )值分析

    BDLocation类,封装了定位SDK的定位结果,在BDLocationListener的onReceive方法中获取.通过该类用户可以获取error code,位置的坐标,精度半径等信息.具体方法 ...

  3. (转)fiddler使用简介--其二

    原文地址:http://www.cnblogs.com/miantest/p/7290176.html 在上一篇中介绍了Fiddler的基本使用方法.通过上一篇的操作我们可以直接抓取浏览器的数据包.但 ...

  4. 标准c内存函数的使用方法

    标准c内存函数 calloc 语法:     #include <stdlib.h>   void *calloc( size_t num, size_t size ); 功能: 函数返回 ...

  5. SQLServer数据库基本操作,导入Excel数据

    打开SQLServer客户端,连上服务端 先建立数据库,点击新建查询 基本操作如下 创建表 create table mytest ( id int primary key identity(1,1) ...

  6. django.contirb

    Django标准库   Django的标准库存放在 django.contrib 包中.每个子包都是一个独立的附加功能包. 这些子包一般是互相独立的,不过有些django.contrib子包需要依赖其 ...

  7. asp.net IRequiresSessionState

    在一般处理程序中,使用context.Session对象,必须先继承IRequiresSessionState接口. System.Web.SessionState.IRequiresSessionS ...

  8. Mycat实现Mysql数据库读写分离

    Linux和Windows环境下搭建Mycat数据读写分离 前提需要:1.服务器装有JVM虚拟机,就是JDK.2.两个Mysql数据库已经实现主从复制,参考:https://www.cnblogs.c ...

  9. 无法处理文件 MainForm.resx,因为它位于 Internet 或受限区域中,或者文件上具有 Web 标记。要想处理这些文件,请删除 Web 标记

    无法处理文件 MainForm.resx,因为它位于 Internet 或受限区域中,或者文件上具有 Web 标记.要想处理这些文件,请删除 Web 标记 问题: 由于文件锁定,VS不能正常读取. 解 ...

  10. 计算机网络概述 传输层 TCP流量控制

    TCP流量控制 所谓流量控制就是让发送发送速率不要过快,让接收方来得及接收.利用滑动窗口机制就可以实施流量控制.通过运用TCP报文段中的窗口大小字段来控制,发送方的发送窗口不可以大于接收方发回的窗口大 ...