行为型模式,这些模式关注对象之间的通信和交互,旨在解决对象之间的责任分配和算法的封装。共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
职责链模式也叫做责任链模式,是一种行为型模式,用于将一个请求传递给一个链中的若干对象,哪个对象适合处理这个请求就让哪个对象来处理。
一个关于涨薪审批的范例
通过一个示例来学习责任链模式。公司员工要求加薪,员工要求加薪小于1000元,部门经理直接审批通过;加薪1000-5000元,技术总监审批通过;加薪5000-10000元,总经理审批通过,代码实现如下:
cpp
class SalaryHandler
{
public:
// 处理加薪请求
// 处理逻辑:0-1000,部门经理审批;
// 1000-5000,技术总监审批;
// 5000以上,总经理审批
void raiseRequest(const string& sname, int salfigure)
{
if (salfigure < 1000)
{
SPdepManager(sname, salfigure);
}
else if (salfigure <= 5000)
{
SPCTO(sname, salfigure);
}
else
{
SPgenManager(sname, salfigure);
}
}
private:
// 部门经理-> 技术总监-> 总经理
// 部门经理审批
void SPdepManager(const string& sname, int salfigure)
{
cout << sname << "的加薪要求为:" << salfigure << "元,部门经理审批通过!" << endl;
}
// 技术总监审批
void SPCTO(const string& sname, int salfigure)
{
cout << sname << "的加薪要求为:" << salfigure << "元,技术总监审批通过!" << endl;
}
// 总经理审批
void SPgenManager(const string& sname, int salfigure)
{
cout << sname << "的加薪要求为:" << salfigure << "元,总经理审批通过!" << endl;
}
};
void test()
{
SalaryHandler sh;
sh.raiseRequest("张三", 800);
sh.raiseRequest("李四", 2000);
sh.raiseRequest("王五", 6000);
/*
张三的加薪要求为:800元,部门经理审批通过!
李四的加薪要求为:2000元,技术总监审批通过!
王五的加薪要求为:6000元,总经理审批通过!
*/
}
上面的代码显然不够灵活,假如现在员工要求加薪超过10000元,需要让公司股东审批,需要在raiseRequest 中加入一个if else 分支,同时需要增加一个公司股东的处理流程方法,这明显不符合开闭原则。
这样的情况没有更好的解决方法呢?有,那就是采用责任链模式解决。部门经理,技术总监,总经理都继承自同一个父类,让部门经理、技术总监和总经理的审批流程都用链表方式链起来,将员工的加薪请求沿着这个链传递,部门经理能处理就处理,不能处理就让责任链的下一个对象处理,直到处理完这个流程。
cpp
namespace _sp2
{
// 薪水
class RaiseRequest
{
public:
RaiseRequest(const string &sname,int salfigure) :m_sname(sname), m_salfigure(salfigure) {}
// 获取请求加薪的人员名字
const string& getName() const
{
return m_sname;
}
// 请求加薪的数额
int getFigure() const
{
return m_salfigure;
}
private:
string m_sname; // 请求加薪的人员名字
int m_salfigure; // 请求加薪的数额
};
// 薪水审批着父类
class SalaryApproverParent
{
public:
SalaryApproverParent() : m_next(nullptr) {}
// 虚析构
virtual ~SalaryApproverParent()
{
;
}
// 设置责任链下一个审批者
void setNext(SalaryApproverParent* next)
{
m_next = next;
}
public:
// 处理加薪请求
virtual void processRequest(const RaiseRequest& req) = 0;
// 找链中的下个对象并把请求投递给下个链中对象
void sendRequestToNextHandler(const RaiseRequest& req)
{
// 找链中的下个对象
if (m_next != nullptr)
{
m_next->processRequest(req);
}
else
{
// 没找到链中下个对象,程序流程执行这里似乎不应该
cout << req.getName() << "的加薪要求为:" << req.getFigure() << "元,没有人审批通过!" << endl;
}
}
private:
SalaryApproverParent* m_next;
};
//部门经理子类
class SalaryApproverDepManager : public SalaryApproverParent
{
public:
virtual void processRequest(const RaiseRequest& req)
{
int figure = req.getFigure();
if (figure <= 1000)
{
cout << req.getName() << "的加薪要求为:" << figure << "元,部门经理审批通过!" << endl;
return;
}
else
{
sendRequestToNextHandler(req);
}
}
};
// 技术总监子类
class SalaryApproverCTO : public SalaryApproverParent
{
public:
// 处理加薪请求
virtual void processRequest(const RaiseRequest& req)
{
int figure = req.getFigure();
if (figure <= 5000)
{
cout << req.getName() << "的加薪要求为:" << figure << "元,技术总监审批通过!" << endl;
return;
}
else
{
sendRequestToNextHandler(req);
}
}
};
// 总经理子类
class SalaryApproverGenManager : public SalaryApproverParent
{
public:
virtual void processRequest(const RaiseRequest& req)
{
int figure = req.getFigure();
if (figure > 5000)
{
//如果自己能处理,则自己处理
cout << req.getName() << "的加薪要求为:" << figure << "元,总经理审批通过!" << endl;
}
else
{
//自己不能处理,尝试找链中的下个对象来处理
sendRequestToNextHandler(req);
}
}
};
void test()
{
// 创建责任链
SalaryApproverDepManager *pdepManager = new SalaryApproverDepManager;
SalaryApproverCTO *pCTO = new SalaryApproverCTO;
SalaryApproverGenManager *pgenManager = new SalaryApproverGenManager;
// 将这些对爱那个串在一起
pdepManager->setNext(pCTO);
pCTO->setNext(pgenManager);
pgenManager->setNext(nullptr);
// 创建加薪请求
RaiseRequest req1("张三", 800);
RaiseRequest req2("李四", 2000);
RaiseRequest req3("王五", 6000);
// 开始处理,从责任链的开始处理
pdepManager->processRequest(req1);
pdepManager->processRequest(req2);
pdepManager->processRequest(req3);
// 释放资源
delete pdepManager;
delete pCTO;
delete pgenManager;
/*
张三的加薪要求为:800元,部门经理审批通过!
李四的加薪要求为:2000元,技术总监审批通过!
王五的加薪要求为:6000元,总经理审批通过!
*/
}
}
从上述代码可以看到,把pdepManager、pCTO、pgenManager这3个分别代表部门经理、技术总监、总经理的对象链到一起构成职责链,其实这3个对象谁在链的开头,谁在链的中间或者结尾都行,后续针对张三(包括李四或王二)的加薪请求,从职责链的开始位置遍历职责链中的每个对象,如果该对象能处理该加薪请求就处理,处理不了沿着职责链寻找下个对象并判断下一个对象能否处理,一直寻找到职责链中最后一个对象。如果职责链中的所有对象全都处理不了这个加薪请求,那么会提示这个加薪无人能够审批。
引入职责链(Chain Of Responsibility)模式
引人"职责链"设计模式的定义(实现意图):使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链(构成对象链),并沿着这条链传递该请求,直到有一个对象处理它为止。
(1) Handler(处理者)。定义了处理请求的接口(processRequest),也记录了下一个处理者是谁(范例中用m_next来记录)。这里指SalaryApproverParent类。
(2) ConcreteHandler(具体处理者)。用于实现针对具体请求的处理,如果自身无法处理请求,则会把请求传递给下一个处理者(后继者)。这里指SalaryApproverDepManager、SalaryApproverCTO类和SalaryApproverGenManager类。
(3)Client(请求者/客户端)。用于向职责链上的具体处理者对象提交处理请求。这里指test主函数中的代码。
责任链特点:
特点1:职责链模式应用的一个请求可能有多个接收者(扮演处理者角色),但最后只有一个接收者会处理该请求。此时,请求的发送者和接收者之间是解耦的。换句话说,请求的发送者只需要将请求发送到链上,并不关心请求的处理细节以及请求的传递也并不知道最终会被哪个接收者处理,这种灵活性可以更好地应对变化。
特点2:职责链一般是一条直线,职责链上的每个接收者仅需要保存一个指向其后继者的指针(不需要保存其他所有接收者的指针),当然,读者可能也会见到环形或者树形结构的职责链(非直线的职责链建链时要小心,不要造成循环链导致请求传递陷入死循环,从而使整个程序的执行陷入死锁)。同时,可以在程序运行期间动态地添加修改或删除职责链上的接收者对象,使针对请求的处理更具灵活性,这是职责链模式的重要特色。
特点3:增加新的处理者不需要修改原有的代码,只需要增加新的具体处理者子类并在客户端重新建链即可,符合开闭原则。
特点4:如果请求传递到职责链的末尾仍然没有得到处理,则应该有一个合理的默认处理方式(书写一个终极处理器并始终将该处理器放在职责链的末尾)或者给出提示信息这属于接收者的责任。
特点5:如果职责链比较长,而能够处理请求的接收者在职责链中又比较靠后,则可能会导致请求处理的延迟;如果需要非常快的请求处理速度,就要权衡是否使用职责链模式。
特点6:可以分别选择不同的接收者对象创建多条不同的职责链以增加接收者在职责链模式中的复用性。
单纯与非单纯的职责链模式
在上述职责链模式的讲解中,已经注意到,虽然一个请求可能有多个接收者,但最后只有一个接收者会处理该请求,一旦该请求得到处理,这个请求就不会再沿着职责链向下传递了,这种对职责链的使用方式称为单纯的职责链模式(职责链模式的定义描述的正是这种情形)。在单纯的职责链模式中,也要求一个请求必须被某一个处理者对象所处理,不能出现一个请求未被任何处理者对象处理的情形。
非单纯的职责链模式与单纯的职责链模式不同。非单纯的职责链模式中允许一个请求被某个接收者处理后继续沿着职责链传递,其他处理者仍有机会继续处理该请求,这样的职责链往往也被称为功能链,即便一个请求未被任何处理者对象处理,在非单纯的职责链模式中也是允许的。功能链一般用于权限的多次多重校验、数据的多重检查和过滤等场合。
下面给出这个敏感词过滤器范例(参照前面的范例书写即可)。该过滤器能够把聊天内容中涉及性、脏话、政治内容的关键词寻找出来并用一些其他符号来代替。首先创建敏感词过滤器父类,代码如下;
cpp
//敏感词过滤器父类
class ParWordFilter
{
public:
ParWordFilter() :m_next(nullptr) {}
virtual ~ParWordFilter()
{
}
// 设置指向的责任链中的下一个过滤器
void setNextChain(ParWordFilter* next)
{
m_next = next;
}
// 处理敏感词过滤请求
virtual string doFilter(string content) = 0;
protected:
string sendRequestToNextHandler(string content)
{
if (m_next != nullptr)
{
return m_next->doFilter(content);
}
return content;
}
private:
ParWordFilter* m_next;
};
// 脏话过滤器
class DirtyWordFilter : public ParWordFilter
{
public:
virtual string doFilter(string content)
{
cout << "通过与词库比对,在content中查找\"脏话\"并用***来替换!" << endl;
content += "***"; //测试代码,具体的实现逻辑略......
return sendRequestToNextHandler(content);
}
};
// 性敏感词过滤器
class SexyWordFilter : public ParWordFilter
{
public:
virtual string doFilter(string content)
{
cout << "通过与词库比对,在content中查找\"性\"敏感词并用XXX来替换!" << endl;
content += "XXX"; //测试代码,具体的实现逻辑略......
return sendRequestToNextHandler(content);
}
};
// 广告过滤器
class AdWordFilter : public ParWordFilter
{
public:
virtual string doFilter(string content)
{
cout << "通过与词库比对,在content中查找\"广告\"敏感词并用YYY来替换!" << endl;
content += "YYY"; //测试代码,具体的实现逻辑略......
return sendRequestToNextHandler(content);
}
};
void test()
{
// 创建责任链
ParWordFilter* pDirtyWordFilter = new DirtyWordFilter;
ParWordFilter* pSexyWordFilter = new SexyWordFilter;
ParWordFilter* pAdWordFilter = new AdWordFilter;
// 将这些对象串在一起
pDirtyWordFilter->setNextChain(pSexyWordFilter);
pSexyWordFilter->setNextChain(pAdWordFilter);
pAdWordFilter->setNextChain(nullptr);
// 创建过滤请求
string strContent = "这是一段内容,内容中包含脏话、性敏感词和广告词";
cout << "原始内容:" << strContent << endl;
// 开始处理,从责任链的开始处理
string strResult = pDirtyWordFilter->doFilter(strContent);
cout << "过滤后的内容:" << strResult << endl;
/*
原始内容:这是一段内容,内容中包含脏话、性敏感词和广告词
通过与词库比对,在content中查找"脏话"并用***来替换!
通过与词库比对,在content中查找"性"敏感词并用XXX来替换!
通过与词库比对,在content中查找"广告"敏感词并用YYY来替换!
过滤后的内容:这是一段内容,内容中包含脏话、性敏感词和广告词***XXXYYY
*/
}
从结果可以看到,聊天内容(请求)先经过性敏感词过滤器处理,然后再把处理后的聊天内容(请求)传递给脏话敏感词过滤器,脏话敏感词过滤器处理完后再传递给政治敏感词过滤器,这些过滤器形成了一个链条。链条上的每个过滤器各自承担自己的处理职责,经过多次被处理并被放行传递到下一个过滤器的过程,最终处理结果被返回到strWordFilterResult中。
职责链模式也常用在视窗系统的窗口中,一个窗口中一般会有若干个控件,例如,有普通按钮、文本编辑框,下拉框、单选框、复选框等,窗口中的所有控件就是一个一个的接收者它们按照先后次序构成职责链(在VisualStudio2019中如果使用MFC来设计窗口,可以使用"格式"一"Tab键顺序"菜单命令指定窗口中各个控件的顺序,这就等同于指定职责链中各个接收者对象的顺序)。当在这样的窗口中单击时,单击事件(请求)就通过职责链来传播,当单击事件传播到某个控件时,该控件一般需要做两件事情:
判断自身是否被禁用,如果被禁用,则将单击事件沿着职责链继续传递;
如果自身未被禁用,则判断单击的位置是否正好在控件自身上,若不在,则将单击事件沿着职责链继续传递;若正好在控件自身上,则触发该控件的单击事件(一般是调用一个叫作OnClick的方法),同时不再将单击事件继续向下传递。