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

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

js职责链模式-LMLPHP

主要涉及两个角色:

  • 抽象处理者角色(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());
05-07 15:00