在实际建立工作流模型时,往往会有很多相似步骤。相似步骤间的逻辑,往往又会包括大量的if..else..判断。
例如,我们先看一个简单的报销流程。它很可能会是这样的:
看似很简单的四个步骤。但客户会告诉我们: 部门经理应该分为两个步骤,因为有些部门设立了“副经理”职位,有些部门甚至“经理”职位空缺,只设“副经理”。换言之,“经理”与“副经理”至少有一个。
于是我们将流程图改为以下情况:
我们发现,增加了一个步骤后,需要增加四条“流程路径”。例如,按照途中的说明,当流程以及流转到了“部门副经理”时,取得下一个步骤需要做一次“if…else…”。如果当前部门有“部门经理”,则下一步流转到“部门经理”步骤;如果当前部门没有“部门经理”,则直接流转到“会计”步骤。
这似乎能够满足客户的要求。但客户的组织机构往往非常复杂,他很可能在某一个时刻又告诉我们:我们共有两级部门,一共四个经理职位,均需要按照上述方式进行流转。
这是非常清晰明了的一个需求。于是我们又继续修改流程模型为:
此时我们就陷入了困境。这新增的两个步骤,带来了更多的逻辑关系,且每个逻辑关系都更为复杂。
例如,假如当前流程以及流转到了“二级部门副经理”处,工作流平台取得下一步骤需要做的判断至少有:
- 如果该部门有“二级部门经理”,则流转到“二级部门经理”;
- 如果该部门没有“二级部门经理”,但有“一级部门副经理”,则流转到“一级部门副经理”;
- 如果该部门没有“二级部门经理”,也没有“一级部门副经理”,但有“一级部门经理”,则流转到“一级部门经理”
- 如果该部门没有“二级部门经理”,也没有“一级部门副经理”,也没有“一级部门经理”,则流转到“会计”步骤
可以看到,仅仅“二级部门副经理”这一个步骤的“选择下一步骤”逻辑,就包含了4个“if…else…”判断,这是非常丑陋的。开发组往往兵强马壮,此种情况也可以通过大量的人工来保证。不过,可以预计的是,客户的多种流程(如报销、列账、冲销、预付等),都有类似的部门经理逻辑。大量的重复工作,无法保证不出错。
更为恐怖的是,假如客户有一天,变成了三个层级的部门组织架构了呢? 从“三级部门副经理”开始,一直往上,到“会计”步骤,那是不是仍然采用这样简单粗暴的方式? 再按照这种方式来做的话,也许我们就直接崩溃了。
怎么办呢?
其实,在GoF的23个设计模式中,就已经提到了一个经典的解决方案:职责链模式。
职责链(Chain of Responsibility)模式 责任链模式是一种对象的行为模式【GOF95】。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织链和分配责任。 |
职责链模式的UML图:
这个UML图非常有意思。我们可以注意到,Handler角色自己聚合成自己。这有点类似于《数据结构》里描述的“链式结构”。
抽象处理者(Handler)角色:定义出一个处理请求的接口。如果需要,接口可以定义出一个方法,以设定和返回对下家的引用。这个角色通常由一个抽象类或接口实现。
具体处理者(ConcreteHandler)角色:具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下家。由于具体处理者持有对下家的引用,因此,如果需要,具体处理者可以访问下家。
我们将这个设计模式运用到刚才所提到的实际环境中。
这时,我们的“抽象处理者”(Handler)是一个能够返回下一步步骤名称的处理类。对于上面提到的“二级部门”的情况,我们将会有五个“具体处理者”(ConcreteHandler):二级部门副经理、二级部门经理、一级部门副经理、一级部门经理、会计。 他们属于两个类别:“经理类”与“会计类”。
“会计类”的处理无逻辑判断。即,当步骤选择器流入“会计类”时,直接返回“会计类”的步骤名。
“经理类”就复杂一些,需要判断当前部门各个层级经理的有无、各个层级经理的是否已经审批。这两种判断可以抽象为两个数组参数的比较。我们设定两个数组参数byte[] candidates与byte[] approvers。第一个数组表示,候选步骤的有与无;第二个数字表示,在数据库记录中,所有步骤的当前审批状态.
例如,假如一个部门有“二级部门副经理、二级部门经理、一级部门经理”,没有“一级部门副经理”,即所有职位从小到大排列,第三大的职位空缺,其余职位齐全——则byte[] candidates的值可以设为{1,1,0,1}。在流程刚刚开始时,所有步骤均未审批,byte[] approvers值为{0,0,0,0};假如当前流程已经流转到“二级部门副经理”步骤,则byte[] approvers可以的值为{1,0,0,0}。
业务规则有了,参数有了,具体该如何处理呢?其实就是比较两个数组中相同index对应值的相等与否。
例如,假设当前 candidates为{1,1,0,1},approvers为{1,0,0,0}。从数组索引0开始,依次往后,我们可以知道“需要”“二级部门副经理”审批,当前“二级部门副经理”已经审批;“需要”“二级部门经理”审批,当前“二级部门经理”还未审批。很明显,流程下一步应该流转到“二级部门经理”步骤。
以上的自然语言描述翻译为算法为:按照索引从小到大的顺序,依次比较candidates数组与approvers数组的元素值是否相等;如果相等,跳到下一个索引处继续比较;如果不等,则返回当前索引所对照的步骤名称。
按照我们设计好的“职责链模式”实现这个整体算法。即:每个“经理”处理者类,只判断属于自己索引的两数组元素,如果相等,则“处理”(返回步骤名);如果不等,则移交下家进行后续判断。
首先构造处理者基类:
abstract class Handler
{
protected int dutyId;//处理者对应的数据键值
protected string stepName;//处理者对应步骤的名称
protected Handler successor;//后继处理者
public void SetSuccessor(Handler successor)
{
this.successor = successor;
}
//处理任务的抽象方法,必须在每个子类进行Override
abstract public string HandleRequest(byte[] candidates, byte[] approvers);
}
构造两个类型的处理者子类:经理子类,会计子类。
/// 会计处理者
/// </summary>
class Accounting : Handler
{
//会计处理者需要赋予“流程步骤名”
public Accounting(string _stepName)
{
stepName = _stepName;
}
//当职责链到达会计步骤时,直接进入会计步骤
override public string HandleRequest(byte[] candidates, byte[] approvers)
{
return stepName;
}
}
/// <summary>
/// 经理处理者
/// </summary>
class Manager : Handler
{
//经理处理者需要赋予“流程步骤名”与对应的“数据键值”
public Manager(int _dutyId, string _stepName)
{
dutyId = _dutyId;
stepName = _stepName;
}
override public string HandleRequest(byte[] candidates, byte[] approvers)
{
if (candidates[dutyId] != approvers[dutyId])
return stepName;
else
if (successor != null)
return successor.HandleRequest(candidates, approvers);
else
throw new ArgumentNullException("没有指定successor!");
}
}
客户端:
public class StepSelector
{
//假设初始情况是:有二级部门副经理、有二级部门经理、
//无一级部门副经理、有一级部门经理
byte[] candidates;
//初始时,所有人均为审批
byte[] approvers;
//传入工作流平台的任务Id,初始化当前状态
StepSelector(string taskId)
{
//◇到业务平台中取得所有的候选步骤
candidates = GetCandidates(taskId);
//例如,初始情况可能是这样的:有二级部门副经理、有二级部门经理、
//无一级部门副经理、有一级部门经理。
//则设置如下:
//candidates = new byte[] { 1, 1, 0, 1 };
//◇到流程平台中取得当前已经完成的步骤
approvers = GetApprovers(taskId);
//初始时,所有人均为审批
//approvers = new byte[] { 0, 0, 0, 0 };
}
//客户端主要方法: 传入整体数据、当前状态,取得下一步骤的名称
public string GetNextStep(byte[] candidates, byte[] approvers)
{
Manager LevelTwoViceManager = new Manager(0, "二级部门副经理审批");
Manager LevelTwoManager = new Manager(1, "二级部门经理审批");
Manager LevelOneViceManager = new Manager(2, "二级部门经理审批");
Manager LevelOneManager = new Manager(3, "二级部门经理审批");
Accounting accounting = new Accounting("公司会计审核");
LevelTwoViceManager.SetSuccessor(LevelTwoManager);
LevelTwoManager.SetSuccessor(LevelOneViceManager);
LevelOneViceManager.SetSuccessor(LevelOneManager);
LevelOneManager.SetSuccessor(accounting);
//从最低值为“二级部门副经理”开始处理请求
return LevelTwoViceManager.HandleRequest(candidates, approvers);
}
}
请注意“客户端”的代码,它完完全全地示例了职责链模式的调用方法。总体分为三步:1. 实例化处理者。2. 设置后继关系 3.从最前端的处理者开始处理。
值得一提的是,这样的结构是符合“开放封闭原则”的(对修改封闭,对扩展开放)。也就是说,无论是需要两个部门经理来审批,还是四个部门经理来审批,甚至扩展到六个部门经理审批,都是不需要修改已有的业务逻辑代码,只需要在客户端中增加更多的处理者类实例、增加每个类的后继处理者、设置参数,就可以了。