一、责任链模式概述
责任链模式定义(Chainof Responsibility Pattren):避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
有时,可以处理某种请求的对象不止一个,而且随着请求的等级的不同,能够处理它的对象也不同。如在公司中,主任可以审批1万元以内的采购单,经理可以审批5万元以下的采购单,总经理可以审批10万元以内的采购单等等。如果我们用责任链来模拟采购单的审批过程,我们就应该把主任、经理、总经理三个对象连接成一条线。当有采购单要审批时,首先由主任接收,如果金额大于1万,则主任对象直接处理,否则主任应该将采购单传递给经理,如果金额在10万元以内,则经理对象就进行处理,否则经理将其传递给总经理…这样就构成了一条简单的责任链。在责任链上,每个对象都是请求处理者,通常每个请求都应该由责任链开头的对象接收,此对象接收到请求后,会先判断自己是否能处理该请求,如果能,则直接处理,并终止请求的传递;如果不能处理,则将该请求传递给责任链的下一个后继对象,依次类推,直到有某个对象能处理该请求为止。客户不必关心请求的处理过程,只需要将请求发送给责任链开头处的对象就可以,这样就可以将请求的发送者和处理者解耦。
下面是一个典型的责任链模式结构图
从这个结构图中可以看到,责任链模式主要包含以下角色:
1、Handler(抽象处理者):该类定义了一个处理请求的接口,其中定义了抽象的请求处理方法,具体处理者将继承此类,并根据自己的需要重写其中的请求处理方法。此类中还定义了一个Handler类型的successor对象,此对象用于引用责任链中的直接后继对象。通过一级接一级的引用,就形成发一条完整的责任链。
2、ConcreteHandler(具体处理者):此类继承抽象处理者类,实现抽象处理者中定义的请求处理方法。它在处理请求前,要判断它是否有权限处理该请求,如果有,则处理之;否则将请求传递给其直接后继,其直接后继也履行相同的程序。
3、Client(客户类):该类是请求的发起者,客户类只需要将请求发送到责任链上,而不用关心责任链如何处理该请求。
二、责任链模式案例
例1、某公司新开发的一款网络游戏——虚拟现实,在这个虚拟世界里,游戏玩家可以得到他们在现实中想入非非而却得不到的东西。为了吸引更多的玩家来玩,该公司上上下下的开发人员、宣传人员都绞尽脑汁。游戏玩家可以单独进入虚拟世界里,也可以组队进入,不管多少人登录,服务器总是把他们当成一个团队。当然这些虚拟世界是有等级的,高级玩家可以独自进入更高级的世界里,而低级玩家需要和朋友组队,才能进入高级的世界里,否则只能进入初级世界里。所以朋友你还在犹豫什么,多叫几个朋友和你一起玩,这样即使你游戏等级低,也能领略到神奇的高级虚拟世界哦。由于系统是根据玩家的等级和团队人数来决定他们可以进入哪种类型的虚拟世界的,所以开发小组决定用责任链模式来处理玩家的登录。下面我们就模拟这个登录的处理过程,当然为简明起见,在此我把问题简单化了,所以就先把等级之类的条件忽略了,只按团队人数来讨论。
如果团队人数为少于3 ,则只能进入玄幻世界;如果团队人数大于3而小于5,则可以进入盗梦空间;团队人数大于5,则可进入太虚幻境。不同的世界要通过不同的轮回隧道(CycleTunnel)传送游戏角色进入。下面是这个案例的类图
下面是其实现代码
1、抽象轮回隧道,该类中主要做的是定义其后继隧道的对象nextTunnel,其中的抽象方法handleLogin()是用于处理玩家登录请求的,具体处理办法由其子类去实现。
package cn.org.lion;
public abstract class CycleTunnel {
protected String tunnelName;
protected CycleTunnel nextTunnel;
public CycleTunnel(String tunnelName)
{
this.tunnelName = tunnelName;
}
public void setNextTunnel(CycleTunnel nextTunnel)
{
this.nextTunnel = nextTunnel;
}
public abstract void handleLogin(Login login);
}
2、具体隧道类,该隧道通往玄幻世界
package cn.org.lion;
public class FantasyTunnel extends CycleTunnel{
public FantasyTunnel(String tunnelName)
{
super(tunnelName);
}
public void handleLogin(Login login)
{
if(login.getTeamNum() < 5)
{
System.out.println("Hello " + login.getTeamName() + ", you have " + login.getTeamNum()
+ " people, you'll arrive in the " + tunnelName + " soon, have a good time!");
}
else
{
if(this.nextTunnel != null)
{
this.nextTunnel.handleLogin(login);
}
}
}
}
3、具体隧道类,该隧道通往盗梦空间
package cn.org.lion;
public class InceptionTunnel extends CycleTunnel{
public InceptionTunnel(String tunnelName)
{
super(tunnelName);
}
public void handleLogin(Login login)
{
if(login.getTeamNum() < 10)
{
System.out.println("Hello " + login.getTeamName() + ", you have " + login.getTeamNum()
+ " people, you'll arrive in the " + tunnelName + " soon, have a good time!");
}
else
{
if(this.nextTunnel != null)
{
this.nextTunnel.handleLogin(login);
}
}
}
}
4、具体隧道类,该隧道通往太虚幻境
package cn.org.lion;
public class UnrealTunnel extends CycleTunnel{
public UnrealTunnel(String tunnelName)
{
super(tunnelName);
}
public void handleLogin(Login login)
{
System.out.println("Hello " + login.getTeamName() + ", you have " + login.getTeamNum()
+ " people, you'll arrive in the " + tunnelName + " soon, have a good time!");
}
}
5、玩家登录类,用于封装登录的团队的基本信息
package cn.org.lion;
public class Login {
private String teamName;
private int teamNum;
public Login(String teamName, int teamNum)
{
this.teamName = teamName;
this.teamNum = teamNum;
}
public void setTeamName(String teamName)
{
this.teamName = teamName;
}
public void setTeamNum(int teamNum)
{
this.teamNum = teamNum;
}
public String getTeamName()
{
return (this.teamName);
}
public int getTeamNum()
{
return (this.teamNum);
}
}
6、测试程序
package cn.org.lion;
public classClient {
public static void main(String argv[])
{
CycleTunnelfnatasyTunnel, inceptionTunnel, unrealTunnel;
fnatasyTunnel= newFantasyTunnel("FnatasyTunnel");
inceptionTunnel= newInceptionTunnel("InceptionTunnel");
unrealTunnel= newUnrealTunnel("UnrealTunnel");
//接下来两代码用于构建责任链,这是至关重要的一步
fnatasyTunnel.setNextTunnel(inceptionTunnel);
inceptionTunnel.setNextTunnel(unrealTunnel);
Loginlogin_1 = newLogin("Running",2);
//登录请求一定要传递到责任链接的第一个对象,
//由它决定直接处理或因无法处理而向后传递
fnatasyTunnel.handleLogin(login_1);
Loginlogin_2 = newLogin("Lion",8);
fnatasyTunnel.handleLogin(login_2);
Loginlogin_3 = newLogin("MonaLisa", 50);
fnatasyTunnel.handleLogin(login_3);
}
}
例2、假设我们在设计一个 OA 系统时,要为该系统设计一个优先级队列,以处理随机发生的用户请求,且系统能够根据事件的优先级处理这些事件(事件的优先级越高,先被处理的可能性越大)。为实现这个优先级队列,我先给出下面的这个队列结构图。每次有事件到来时,它都会先被发送给队列的初始结点 Queue_0 ,如果该事件的优先级与 Queue_0 定义的优先级相同,那么该事件就会被链接到 Queue_0 的末尾,等待系统的最终处理;如果该事件的优先级比 Queue_0 定义的优先级高,那么它就会被 Queue_0 传递到 Queue_1,如果 Queue_1 … 如此类推,事件要么被某个队列接收,要么继续被向后传递,最终事件总会被链接到某个队列的末尾。显然这和责任链接模式有很多相似之处,所以我们采用责任链模式来处理事件的接收问题。为了简明起见,我假设只有队列的优先级只有三个,分别定义为QueueA、QueueB、QueueC。
由上边的论述,易得出优先级队列的类图,如下
其实现源码如下:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
// 事件
class Event
{
public:
string message;
int priority;
//为省篇幅,此处略去 get()、set()...
};
// 优先级队列父类
class Queue
{
protected:
Queue *nextQueue;
int priority;
public:
Queue(int temp)
{
priority = temp;
}
void SetNext(Queue *next)
{
nextQueue = next;
}
virtual void handleEvent(Event *event) = 0;
};
class QueueA : public Queue
{
public:
QueueA(int priority) : Queue(priority){}
virtual void handleEvent(Event *event)
{
if (0 == event->priority)
{
cout << "QueueA receive event" << endl;
}
else
{
nextQueue->handleEvent(event);
}
}
};
class QueueB : public Queue
{
public:
QueueB(int priority) : Queue(priority){}
virtual void handleEvent(Event *event)
{
if(1 == event->priority)
{
cout << "QueueB receive event" << endl;
}
else
{
nextQueue->handleEvent(event);
}
}
};
class QueueC : public Queue
{
public:
QueueC(int priority) : Queue(priority){}
virtual void handleEvent(Event *event)
{
if(event->priority >= 2)
{
cout << "QueueC receive event" << endl;
}
else
{
nextQueue->handleEvent(event);
}
}
};
int main()
{
Queue *queue_a = new QueueA(0);
Queue *queue_b = new QueueB(1);
Queue *queue_c = new QueueC(2);
queue_a->SetNext(queue_b);
queue_b->SetNext(queue_c);
Event *event = new Event();
event->priority = 0;
queue_a->handleEvent(event);
event->priority = 1;
queue_a->handleEvent(event);
event->priority = 5;
queue_a->handleEvent(event);
event->priority = 1;
queue_a->handleEvent(event);
return 0;
}
三、小结
1、优点:可由客户端负责创建责任链,客户端有需要时只需要将请求发给责任链的首结点即可,不必关心该请求将由哪个对象处理,就可以得到处理结果,这降低了系统的耦合度;责任链可以根据需要,灵活的增加或减少责任链的长度。
2、缺点:如果责任链创建得不合理,那么客户的请求可能得不到处理;如果链太长,那么每个请求在链上传递的平均距离就会变长,这会降低系统的性能。