责任链模式
责任链模式是一种行为设计模式, 允许你将请求沿着处理者链进行发送。 收到请求后, 每个处理者均可对请求进行处理, 或将其传递给链上的下个处理者。
即将所有处理某种请求的对象一个接一个的排成序列,当某个任务来临时,按照次序列依次执行下去,直至有对象处理。
意义
责任链模式是一个很贴近生活的设计模式,使每个对象做好自己分内的事情即可。当一个请求来的时候,当前处理对象不能处理,那就交给下一个处理对象,至于是谁处理无需关心。
另外,请求只要被某个对象处理了,就直接返回,不会继续执行下去。从而保证请求不会被多次处理。
应用场景
电脑打开了多个软件窗口(TIM、Chrome、WeChat),三个窗口有重叠的部分。实现鼠标选定不同的软件窗口。
分析
上述场景,鼠标的点击事件可以看成一种请求,当点击到某个软件窗口的范围内则表示选中该软件。如果点击到软件窗口重叠的部分,就默认选中最顶部的软件。
软件实现:将软件从最顶端到最底端排成链型,当收到点击请求时,按照链顺序依次访问软件类,是否需要执行此请求即可。
类图
- CViewBase: 软件窗口类基类
- CChromeView: Chrome界面类
- CTimView: Tim界面类
- CWeChatView: WeChat界面类
实现方式较简单,CViewBase派生出的子类在运行中持有一个CViewBase对象,并在HandleRequest处理外部请求,当此对象不能处理时,便丢给持有的CViewBase对象去处理。
源码实现
编程环境
- 编译环境: Linux环境
- 语言: C 语言
- 编译命令: make
工程结构
代码语言:c 复制ChainOfResponsibility/
├── app_view.cc
├── app_view.h
├── main.cc
└── Makefile
- app_view: 功能类代码
- main.cc: 客户端代码
- Makefile: 编译工具
基类接口
代码语言:c 复制class CViewBase
{
public:
CViewBase()
{}
virtual ~CViewBase()
{}
virtual void SetNextHandler(CViewBase* handler) = 0;
virtual void HandleRequest(void *request) = 0;
};
定义各app窗口接口,主要在于统一各个子类处理请求的接口。
Chrome类接口
代码语言:c 复制class CChromeView : public CViewBase
{
public:
explicit CChromeView(int XBegin, int XEnd, int YBegin, int YEnd);
void SetNextHandler(CViewBase* handler);
void HandleRequest(void *request);
bool IsSelect(SClickLocation *location);
private:
int mXBegin;
int mXEnd;
int mYBegin;
int mYEnd;
CViewBase *mNextHandler;
};
Chrome类定义了此窗口的位置坐标,以及重载了基类的接口HandleRequest。
注册下一责任类
代码语言:c 复制void CChromeView::SetNextHandler(CViewBase* handler)
{
mNextHandler = handler;
}
此接口用于实例化该类持有的CViewBase对象,该对象在责任链上的顺序位于CChromeView之后。
请求处理接口实现
代码语言:c 复制void CChromeView::HandleRequest(void *request)
{
if (!request) {
VIEW_LOGE("request is invaild!n");
}
if (IsSelect((SClickLocation *)request)) {
VIEW_LOGI("Click Chrome!n");
} else {
VIEW_LOGD("Next handler!n");
if (mNextHandler) {
mNextHandler->HandleRequest(request);
}
}
}
bool CChromeView::IsSelect(SClickLocation *location)
{
bool ret = false;
if ( location->locX > mXBegin && location->locX < mXEnd
&& location->locY > mYBegin && location->locY < mYEnd
)
{
ret = true;
}
return ret;
}
当鼠标点击的坐标位于CChromeView内,则处理此请求;否则,丢给下一个类去处理。
其他子类业务与此相同,不再列举。
规定责任链顺序
代码语言:c 复制static void init()
{
// 设置责任链顺序: theTim -> theChrome -> theWeChat
theChrome.SetNextHandler(&theWeChat);
theTim.SetNextHandler(&theChrome);
}
规定请求执行对象的顺序依次为theTim -> theChrome -> theWeChat
客户端接口
代码语言:c 复制int main(int argc, char *argv[])
{
init();
SClickLocation clickEvent1 = {28, 42}; // 点击坐标(28, 42), Chrome
theTim.HandleRequest(&clickEvent1);
SClickLocation clickEvent2 = {48, 62}; // 点击坐标(48, 62), WeChat
theTim.HandleRequest(&clickEvent2);
SClickLocation clickEvent3 = {12, 20}; // 点击坐标(12, 20), Tim
theTim.HandleRequest(&clickEvent3);
return 0;
}
测试效果
代码语言:c 复制$ ./exe
Click Chrome!
Click WeChat!
Click Tim!
模式的扩展
职责链模式存在以下两种情况:
- 纯的职责链模式:一个请求必须被某一个处理者对象所接收,且一个具体处理者对某个请求的处理只能采用以下两种行为之一:自己处理(承担责任);把责任推给下家处理。
- 不纯的职责链模式:允许出现某一个具体处理者对象在承担了请求的一部分责任后又将剩余的责任传给下家的情况,且一个请求可以最终不被任何接收端对象所接收。
总结
- 责任链模式主要在于解耦请求与处理,让请求在责任链上传递,直到有合适的模块处理。其巧妙之处在于将处理者构建为链式节点,允许每个节点自身决定处理或转发特定请求。使请求能够流动起来,同时能够定制请求被处理依次或多次。其实现方式可根据不同的需求有不同的设计,关键在于其模式思想。
优点
- 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
- 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
- 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
- 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
缺点
- 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
- 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
- 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。