审批操做是ERP或OA系统中必不可少的功能之一。这里介绍两种我设计的用于审批操做的方案,并借此就“状态模式”与“策略模式”提出一点本身的理解。
别问我为何不使用工做流引擎等工具来实现审批功能。作初版方案时,我孤陋寡闻得并不知道有这个东西。后来引入工做流框架会致使学习曲线骤然上扬,不太划算。git
背景无需过多介绍,不外乎有一些数据/任务/请求,须要由领导们点一下头或者按钮。github
因为孤陋寡闻,在获得需求以后,我第一反应不是“工做流”,而是“状态机”。它从“提交”状态开始,流经“已初审”“已终审”或者“初审驳回”“终审驳回”等状态,进入终态。
这个状态机以下图所示:
固然,状态机中状态的名称、状态间的流转,是与业务需求紧密相关的。例如,有些业务会要求在“已终审”状态下执行“驳回”操做后进入“终审驳回”状态,而有些则要求返回“已初审”状态。不过万变不离其宗,种种流程最终都能概括到“状态机”中来。
在这个思路下,我用了两种不一样的设计模式来实现需求——状态模式和策略模式,它们都很好的完成了任务。须要多说一句的是,这是两个不一样系统下独立的两次实现,而不是一个系统中的“原始版”和“改进版”。于是,两个方案之间并无很是显著的优劣对比,本文的重点也不是两者的“优劣”对比。设计模式
状态模式
首先来回顾一下咱们常说的状态模式。简便起见,这里只提供类图。
其中的核心是“状态”接口。这个接口中有N个方法,对应的是状态机中的N个状态。每一个方法负责从当前状态迁移到另外一个状态上——通常是别的状态,也能够仍然是当前状态。
每一个具体的状态都继承自这个接口,并在实现类中封装本身所须要的数据、重写本身的状态迁移操做。app
个人方案
在个人设计方案中,类图则是这个样子的:
与“教科书”上的类图类似的,是“状态”接口(Examiner),以及各个实际状态所对应的子类。
与之不一样的是,虽然个人状态机中有五个状态,可是因为每一个状态最多都只有两个状态迁移操做(经过,或者驳回),所以,状态接口中我只定义了两个方法。
还有一点不一样在于,我在Examiner接口下,加了一个默认的实现类(ExaminerAsDefault)。这个类实际上什么都不作,每一个方法都直接抛出UnSupportedOperationException。这个类的做用是简化子类,使得每一个子类只须要重写本身关心的方法,而不须要重写无关方法。固然,Java 8为接口引入的默认方法,能够实现一样的功能,这是后话。此外,因为业务需求中每次只作一步状态迁移,所以Examiner接口不须要再返回本身。还有一点不一样的是,这个方案中,状态迁移操做与状态数据被拆开了——迁移操做由Examiner定义,状态数据则用Dto来封装。框架
扩展
当出现新的状态、或者新的迁移操做怎么办呢?
出现新的状态时,建立一个新的“状态”子类,并实现对应的“状态迁移”方法就好了。出现新的迁移操做时则更简单,只须要作第二步就能够了。ide
个人方案
在个人设计方案中,类图则是这样的:
能够说这是一个“标准”的策略模式类图。接口定义从一个状态到另外一个状态的迁移动做,不一样的子类用不一样的“策略”去实现它——例如从“已提交”到“已初审”,或者从“已初审”到“初审驳回”,等等。
状态相关的数据,仍然由单独的Dto来保存和传递。学习
扩展
策略模式下,如何增长新的状态、新的迁移操做呢?
因为策略模式仅仅定义了“状态迁移”动做,所以,不管是增长新的状态、仍是增长新的迁移操做,都只须要增长对应的子类便可。spa
我并不喜欢比较不一样设计模式之间的区别。但这里仍能够多说几句。
用状态模式实现状态机,大概是一个最直观、最容易想到的设计。可是,标准的状态模式将状态数据也封装到状态类中。这使得这个类没法用单例实现。另外,因为状态接口中,对应每个状态都有一个方法,这可能会使得部分子类很是的大。
用策略模式实现状态机,与状态机思想是有冲突的。状态机是以“状态”为本,状态迁移操做为辅;而策略模式却专一于状态迁移操做,“状态”的概念淡化得几乎消失了。此外,与状态模式中的“超级类”相反,策略模式可能致使“类爆炸”。
两种模式之间的分界线,也许只是概念上的“以状态为本”或“以操做为本”。就实践上来讲,像个人方案中那样,将状态模式中的数据与操做拆分开,那么整个方案与策略模式其实相去无几。
这是我不喜欢比较不一样设计模式之间区别的缘由。因为设计模式的变化、组合很是多,不少时候不一样设计模式之间的界限仅仅存在于概念上、思想上,而不在实践中。费尽心思去分析“如何区分23种设计模式”,只在学习阶段有一点意义。咱们更应该关注设计模式适用的业务场景、业务问题,以及如何实现它们。
毕竟,科学能够知足于“认识世界”,技术必需要以“改造世界”为目标。设计