使用职责链模式简化工作流相似步骤间的逻辑

摘要:
在实际建立工作流模型时,往往会有很多相似步骤。相似步骤间的逻辑,往往又会包括大量的if..else..判断。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。职责链模式的UML图:这个UML图非常有意思。“会计类”的处理无逻辑判断。很明显,流程下一步应该流转到“二级部门经理”步骤。按照我们设计好的“职责链模式”实现这个整体算法。

在实际建立工作流模型时,往往会有很多相似步骤。相似步骤间的逻辑,往往又会包括大量的if..else..判断。

例如,我们先看一个简单的报销流程。它很可能会是这样的:

image

看似很简单的四个步骤。但客户会告诉我们: 部门经理应该分为两个步骤,因为有些部门设立了“副经理”职位,有些部门甚至“经理”职位空缺,只设“副经理”。换言之,“经理”与“副经理”至少有一个。

于是我们将流程图改为以下情况:

image

我们发现,增加了一个步骤后,需要增加四条“流程路径”。例如,按照途中的说明,当流程以及流转到了“部门副经理”时,取得下一个步骤需要做一次“if…else…”。如果当前部门有“部门经理”,则下一步流转到“部门经理”步骤;如果当前部门没有“部门经理”,则直接流转到“会计”步骤。

这似乎能够满足客户的要求。但客户的组织机构往往非常复杂,他很可能在某一个时刻又告诉我们:我们共有两级部门,一共四个经理职位,均需要按照上述方式进行流转。

这是非常清晰明了的一个需求。于是我们又继续修改流程模型为:

image

此时我们就陷入了困境。这新增的两个步骤,带来了更多的逻辑关系,且每个逻辑关系都更为复杂。

例如,假如当前流程以及流转到了“二级部门副经理”处,工作流平台取得下一步骤需要做的判断至少有:

  1. 如果该部门有“二级部门经理”,则流转到“二级部门经理”;
  2. 如果该部门没有“二级部门经理”,但有“一级部门副经理”,则流转到“一级部门副经理”;
  3. 如果该部门没有“二级部门经理”,也没有“一级部门副经理”,但有“一级部门经理”,则流转到“一级部门经理”
  4. 如果该部门没有“二级部门经理”,也没有“一级部门副经理”,也没有“一级部门经理”,则流转到“会计”步骤

可以看到,仅仅“二级部门副经理”这一个步骤的“选择下一步骤”逻辑,就包含了4个“if…else…”判断,这是非常丑陋的。开发组往往兵强马壮,此种情况也可以通过大量的人工来保证。不过,可以预计的是,客户的多种流程(如报销、列账、冲销、预付等),都有类似的部门经理逻辑。大量的重复工作,无法保证不出错。

更为恐怖的是,假如客户有一天,变成了三个层级的部门组织架构了呢? 从“三级部门副经理”开始,一直往上,到“会计”步骤,那是不是仍然采用这样简单粗暴的方式? 再按照这种方式来做的话,也许我们就直接崩溃了。

怎么办呢?

其实,在GoF的23个设计模式中,就已经提到了一个经典的解决方案:职责链模式

职责链(Chain of Responsibility)模式
责任链模式是一种对象的行为模式【GOF95】。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织链和分配责任。

职责链模式的UML图:

_OEZ3HQO2}~EI1E94FEE1DT

这个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数组的元素值是否相等;如果相等,跳到下一个索引处继续比较;如果不等,则返回当前索引所对照的步骤名称。

按照我们设计好的“职责链模式”实现这个整体算法。即:每个“经理”处理者类,只判断属于自己索引的两数组元素,如果相等,则“处理”(返回步骤名);如果不等,则移交下家进行后续判断。

首先构造处理者基类:

使用职责链模式简化工作流相似步骤间的逻辑第5张使用职责链模式简化工作流相似步骤间的逻辑第6张处理者基类
//处理者基类abstractclassHandler
{
protectedintdutyId;//处理者对应的数据键值protectedstringstepName;//处理者对应步骤的名称protectedHandler successor;//后继处理者publicvoidSetSuccessor(Handler successor)
{
this.successor =successor;
}
//处理任务的抽象方法,必须在每个子类进行OverrideabstractpublicstringHandleRequest(byte[] candidates, byte[] approvers);
}

构造两个类型的处理者子类:经理子类,会计子类。

使用职责链模式简化工作流相似步骤间的逻辑第7张使用职责链模式简化工作流相似步骤间的逻辑第8张具体处理者
///<summary>///会计处理者
///</summary>classAccounting : Handler
{
//会计处理者需要赋予“流程步骤名”publicAccounting(string_stepName)
{
stepName
=_stepName;
}
//当职责链到达会计步骤时,直接进入会计步骤overridepublicstringHandleRequest(byte[] candidates, byte[] approvers)
{
returnstepName;
}
}
///<summary>///经理处理者
///</summary>classManager : Handler
{
//经理处理者需要赋予“流程步骤名”与对应的“数据键值”publicManager(int_dutyId, string_stepName)
{
dutyId
=_dutyId;
stepName
=_stepName;
}
overridepublicstringHandleRequest(byte[] candidates, byte[] approvers)
{
//如果两个值不相等,说明这个步骤还未处理过。立即返回该步骤的名称。if(candidates[dutyId] !=approvers[dutyId])
returnstepName;
elseif(successor !=null)
returnsuccessor.HandleRequest(candidates, approvers);
elsethrownewArgumentNullException("没有指定successor!");
}
}

客户端:

使用职责链模式简化工作流相似步骤间的逻辑第9张使用职责链模式简化工作流相似步骤间的逻辑第10张客户端(示例)
//客户端publicclassStepSelector
{
//假设初始情况是:有二级部门副经理、有二级部门经理、
//无一级部门副经理、有一级部门经理byte[] candidates;
//初始时,所有人均为审批byte[] approvers;
//传入工作流平台的任务Id,初始化当前状态StepSelector(stringtaskId)
{
//◇到业务平台中取得所有的候选步骤candidates =GetCandidates(taskId);
//例如,初始情况可能是这样的:有二级部门副经理、有二级部门经理、
//无一级部门副经理、有一级部门经理。
//则设置如下:
//candidates = new byte[] { 1, 1, 0, 1 };
//◇到流程平台中取得当前已经完成的步骤approvers =GetApprovers(taskId);
//初始时,所有人均为审批
//approvers = new byte[] { 0, 0, 0, 0 };
}
//客户端主要方法: 传入整体数据、当前状态,取得下一步骤的名称publicstringGetNextStep(byte[] candidates, byte[] approvers)
{
Manager LevelTwoViceManager
=newManager(0, "二级部门副经理审批");
Manager LevelTwoManager
=newManager(1, "二级部门经理审批");
Manager LevelOneViceManager
=newManager(2, "二级部门经理审批");
Manager LevelOneManager
=newManager(3, "二级部门经理审批");
Accounting accounting
=newAccounting("公司会计审核");
LevelTwoViceManager.SetSuccessor(LevelTwoManager);
LevelTwoManager.SetSuccessor(LevelOneViceManager);
LevelOneViceManager.SetSuccessor(LevelOneManager);
LevelOneManager.SetSuccessor(accounting);
//从最低值为“二级部门副经理”开始处理请求returnLevelTwoViceManager.HandleRequest(candidates, approvers);
}
}

请注意“客户端”的代码,它完完全全地示例了职责链模式的调用方法。总体分为三步:1. 实例化处理者。2. 设置后继关系 3.从最前端的处理者开始处理。

值得一提的是,这样的结构是符合“开放封闭原则”的(对修改封闭,对扩展开放)。也就是说,无论是需要两个部门经理来审批,还是四个部门经理来审批,甚至扩展到六个部门经理审批,都是不需要修改已有的业务逻辑代码,只需要在客户端中增加更多的处理者类实例、增加每个类的后继处理者、设置参数,就可以了。

ChainOfResponsibility

免责声明:文章转载自《使用职责链模式简化工作流相似步骤间的逻辑》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Android studio 使用NDK工具实现JNI编程微信小程序报错request:fail url not in domain list下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

dubbo超时重试和异常处理

dubbo超时重试和异常处理   参考: https://www.cnblogs.com/ASPNET2008/p/7292472.html https://www.tuicool.com/articles/YfA3Ub https://www.cnblogs.com/binyue/p/5380322.html https://blog.csdn.net/...

备战秋招[四]-复位

欢迎关注个人公众号摸鱼范式------------------------------------------ 版权声明: 本文作者: 烓围玮未 首发于知乎专栏:芯片设计进阶之路 转发无需授权,请保留这段声明。 ------------------------------------------ 复位 首先上思维导图: 如果要问“芯片中怎么复位才对?”...

机器学习算法与Python实践之(七)逻辑回归(Logistic Regression)

http://blog.csdn.net/zouxy09/article/details/20319673 机器学习算法与Python实践之(七)逻辑回归(Logistic Regression) zouxy09@qq.com http://blog.csdn.net/zouxy09 机器学习算法与Python实践这个系列主要是参考《机器学习实战》这本书...

Java编程的逻辑 (26)

本系列文章经补充和完善,已修订整理成书《Java编程的逻辑》,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http://item.jd.com/12299018.html 包装类 Java有八种基本类型,每种基本类型都有一个对应的包装类。 包装类是什么呢?它是一个类,内部有一个实例变...

springcloud Alibaba 微服务 flowable 工作流 自定义表单 vue.js前后分离

功能模块设计方案 1.代码生成器: [正反双向](单表、主表、明细表、树形表,快速开发利器)freemaker模版技术 ,0个代码不用写,生成完整的一个模块,带页面、建表sql脚本、处理类、service等完整模块2.多数据源:(支持同时连接无数个数据库,可以不同的模块连接不同数的据库)支持N个数据源3.阿里数据库连接池druid,安全权限框架 shiro...

工作流调度器azkaban(以及各种工作流调度器比对)

1:工作流调度系统的作用: (1):一个完整的数据分析系统通常都是由大量任务单元组成:比如,shell脚本程序,java程序,mapreduce程序、hive脚本等;(2):各任务单元之间存在时间先后及前后依赖关系;(3):为了很好地组织起这样的复杂执行计划,需要一个工作流调度系统来调度执行; (4):举例说明工作流调度系统的具体作用:   我们可能有这...