从本文开始我们来介绍行为模式。根据GOF的说法,设计模式可以分为:创建型模式,结构型模式和行为型模式。
行为型模式涉及到算法和对象间职责的分配。行为模式不仅描述对象或类的模式,还描述它们之间的通信。
我们先来介绍第一种行为型模式:职责链。
意图
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
结构图
职责链模式的关键在于“链”,从上面的结构图也可以看到,基类接口有个重要的特性,就是自己维护了一个链。
根据GOF的说法,有多种实现链的方式:
1. 在基类接口中定义链接;
2. 子类中定义链接;
3. 使用已有的链接。
本文使用第一种方式,就是基类接口中定义链接。
举个例子:一个公司里面,一个员工申请用公司的钱买书,或者买其他东西,那么就需要经过领导的审批。组长可以审批,部分经理可以,总经理可以审批。比如组长最多可以审批1000的预算,部门经理可以审批5000的预算,总经理就可以审批任何数目的预算了。
在这个例子里面,组长,部门经理,总经理都有一个共同的能力:审批。
对于普通员工来讲,他只需要提交这个申请给他的直接领导,然后就等结果。我们可以把组长,部门经理,总经理链接起来,就是一个职责链。
员工的这个申请会在这个链中传递,知道有一个对象来处理这个请求,而员工并不需要关心具体哪个对象来处理,他只要等结果就行了。
职责链的关键就在于这条链怎么建立起来。
比如,我们建立一条链,这条链里面只有组长和部门经理,那么这里就有个问题,超过5000的预算得不到审批,也就是没有对象可以处理超过5000的预算请求了。这也是职责链模式的一个问题:一个请求不一定会被处理(链中没有一个合适的对象可以处理这个请求)。
我们用代码来实现一下上面的例子:
class CLeader
{
public:
CLeader(CLeader* leader = 0, float budget = 0):_successor(leader), _budget(budget){}
virtual void ApproveBudget(float budget) = 0;
protected:
CLeader* _successor;
float _budget;
};
class CTeamLeader: public CLeader
{
public:
CTeamLeader(CLeader* leader = 0, float budget = 0):CLeader(leader, budget){}
virtual void ApproveBudget(float budget)
{
if (budget <= _budget)
{
std::cout << "TeamLeader approve process\n";
}
else
{
if (_successor)
{
_successor->ApproveBudget(budget);
}
}
}
};
class CDM: public CLeader
{
public:
CDM(CLeader* leader = 0, float budget = 0): CLeader(leader, budget){}
virtual void ApproveBudget(float budget)
{
if (budget <= _budget)
{
std::cout << "DM approve process\n";
}
else
{
if (_successor)
{
_successor->ApproveBudget(budget);
}
}
}
};
class CGM: public CLeader
{
public:
CGM(CLeader* leader = 0, float budget = 0): CLeader(leader, budget){}
virtual void ApproveBudget(float budget)
{
std::cout << "GM approve process\n";
}
};
CLeader是一个抽象接口,它定义了审批接口:ApproveBudget。同时里面还有2个属性,一个是指向后继者的指针_successor,另外一个是审批上限。
CTeamLeader是组长,CDM(DM: Department Manager)是部门经理, CGM是总经理。在这些类的构造函数里面可以设置每个领导的后继者(也就是当某位领导无法审批的时候,需要提交给上一级审批)和审批上限。
抽象接口CLeader里面的后继者是职责链模式的一个关键。用这个后继者来实现请求在链中传递。
看一下类图,可以增加对这个模式的理解:
可以明显的看到CLeader里面有个successor指引。
看看客户端怎么调用:
CLeader* gm = new CGM();
CLeader* dm = new CDM(gm, 5000);
CLeader* teamleader = new CTeamLeader(dm, 1000);
//teamleader可以审批
teamleader->ApproveBudget(500);
//teamleader不可以审批2000的预算,那么就提交的teamleader的后继者部门经理。部门经理可以审批2000的预算
teamleader->ApproveBudget(2000);
//对于8000的预算,部门经理也不可以审批,那就交给gm(总经理)
teamleader->ApproveBudget(8000);
delete gm;
delete dm;
delete teamleader;
这里,我们创建了3个对象,组长,部门经理和总经理。设置部分经理为组长的后继者,部门经理的后继这是总经理,总经理就没有后继者了,也就是说总经理是这个链的末端。同时设置组长的审批上限是1000块,部门经理是5000块,总经理没有限制。对于员工来讲,500预算申请,2000预算申请和8000预算申请,都是提交给主管他的组长。然后等结果,至于谁来审批,员工无需关心。也就是意味着请求的发送者和接收者解耦了,或者说是松耦合吧。
这就是职责链模式的基本概况。
优点:
1. 降低请求的发送者和接收者的耦合度,发送者只需向一条链发送请求,然后链中的哪个对象来处理,发送者无需关心;
2. 增强了给对象指派职责的灵活性,可以在运行时动态修改链。
缺点:
1. 构造这条链是一个负担;
2. 因为请求没有一个明确的接收者,不能保证请求一定会被这条链正确的处理,有时可能到达链的末端都得不到处理。
再来考虑例子里面的一个问题,我们是否可以让员工将预算请求递交给链的第二或者其他的对象(不是第一个对象)。比如:
//当然也可以让部门经理来审批500的预算,这个就跳过了teamleader,不给teamleader面子啊,呵呵。
//从职责链的角度来讲,链中的任何一个对象都可以处理请求,当然,我们在实现链中的类的时候可以使用一些处理规则,
//比如这里的预算上限,然后根据规则来选择一个合适的handler。
dm->ApproveBudget(500);
//gm就可以审批任何数目的预算了。
gm->ApproveBudget(500);
从模式的角度讲,并不会限制这些。这些都看具体的应用,如果真的有这种需求,当然可以了。比方说把请求提交给链的第二个对象,没什么不可以的。这个就是一些特殊处理。
当我们创建了一条链之后,通常都是把请求提交给链的第一个对象,如果有特殊需要,也可以提交给链中的其他对象。
这个例子的后继者是放在抽象接口中的,其实后继者也有其他的实现方式,这里就不再用代码来模拟了。
相关模式
职责链常和Composite一起使用。这种情况下,一个构件的父构件可以作为它的后继者(也就是无需再额外定义后继者了)。
比如,我们用Composite模式来构造公司里面的员工。普通员工,组长,部门经理,总经理等等都是公司的员工。那么我们就可以用Composite来抽象这些成员,比如普通员工是一个Leaf,然后组长下面有一系列的普通员工,部门经理下面又有一系列的组长,总经理下面就有一系列的部门经理。那么普通员工的父构件组长就是他的后继者,组长的父构件部门经理就是组长的后继者。也就是说Composite模式里面本身就已经有后继者的关系了,在这种系统里面如果要使用职责链模式的话,就无需再额外定义后继者了。