状态机在线上录单系统的实际应用例子

来源:互联网 发布:练字软件免费下载 编辑:程序博客网 时间:2024/06/05 09:56

最近又碰到一个线上录单的系统。上一个是在3月份的时候,项目进度没调节好,需求也不停的变化,造成项目做的很累。

新的这个录单系统,经过考虑,打算使用状态机来创建服务器端应用框架,寄希望于可以将业务代码分离并很好的组织起来,简化开发和测试难度。

系统的目的是记录用户申请借款的请求。大概的流程,是线上录单的人员,将用户通过线下方式提交的请求通过web填入数据库中,然后提交借款资质审核机构,机构同意借款后,将借款申请包装成为一个产品,在线上出售。但是鉴于一些法务问题,在达成交易后,还需要将合同由录单人员交付第三方办理认证,并将认证提交资质审核机构存档。最后交易完成。(由于商业原因,没办法说明得太细)

整个过程,我这里的系统主要负责提供录单界面,记录申请单,调用接口资质审核机构,接受资质审核机构结果,等待产品出售完成消息,提供第三方认证上传界面,上传认证到资质审核机构。

于是,我制定了一个状态机接口,主要又有下方法:

public interface State{void create();void update();void submit();void invested();void certify();}
然后,制作了一个抽象类,实现了这个接口:

public abstract class AbstractState  {protected Request req;//真正要处理的请求;...public void create(){ throw new RuntimeException("Not suppoorted");}public void update(){... //( 和create等其他方法一样,都抛异常)}}

   然后对于不同的状态,编写不同的子类来完成逻辑,例如对于初始状态StartState:

public class StartState extends AbstractState{@Resourceprivate RequestDAO reqDAO;@overridepublic void create(){  Request req = new Request();  reqDAO.insert(req);  req.setStatus(NEW);  super.factory.init(req);//切换一个新的状态}}


因为系统本身使用Spring作为容器,所以另一个问题是,如何将状态机和从数据库取出的DTO对象融合。在这里,我做了以下的工作:

首先创建了一个Spring管理的工厂类,负责创建State对象:

public class StateFactory implements ApplicationContextAware{ApplicationContextAware ctx;public State init(Request req){State state =null;   switch(req.getStatus()){     case NEW:         state = ctx.getBean("newState", NewState.class);     case SUBMIT:        state = ctx.getBean("submitState", SubmitState.class); ...//所有的状态机定义   }req.getState().setRequest(null);req.setState(state);}}

这里的req.getStatus()是申请单对象的状态属性,会存储在数据库中并取出。另外每个状态机实力是单例生成的,因为每个状态机对象要和一个业务对象对于,是有状态的。

于是在Service层,就可以这样方便的操作:

public class RequestService{   @Resource    private RequestDAO reqDAO;   @Resource     private StateFactory factory;   public void submit(Long reqId){     Request req = reqDAO.get(reqId);     factory.init(req);     req.getState().submit();   }}

于是,只要定义不同的实现状态,在状态中实现相应的方法,就可以完成相应的任务。相对而已,在不是用状态机时,我的代码中会看见许多状态判断的if...else...

public class RequestService{    public void submit(Long reqId){      Request req = reqDAO.get(reqId);      if(req.getStatus()== NEW){         reqDAO.updateStatus(reqId, SUBMIT);         //other stuff       }   }   public void certify(Long reqId){      Request req = reqDAO.get(reqId):      if(req.getStatus() == INVESTED){         RemoteService.syncCeriticate(req);         reqDAO.updateStatus(reqId, CERTIFY);      }   }}
而且,如果状态发生改变,这些分散的if...else都要全部分析,需要改的代码会很分散。比如要加一个提交第三方校验驳回的状态,就要找到所有可能相关的状态,挨个修改。另一个恐怖的事情就是状态事件捕获。比如要记录所有状态改变到数据库,或者对于特定的状态,发送短信通知用户。上述的实现,就要在每个方法都加入代码,侵入性极大,而且容易疏漏。但是状态机可以利用观察者模式,在状态机创建的时候,就将侦听接口植入,然后统一处理。 状态机的好处就在于,状态很集中,概念对应的业务也十分清晰。无论从编写和测试,都十分有效。


不过状态机也有局限,如果状态很少,而且也不太会发生改变,就不许要用了,框架不会为业务作出太多贡献,反而搞得累赘。另一个问题是,使用状态机的前提是,可以清楚的描述状态变迁,一般都是针对单一对象的判断。对于一些设计多对象的过程性的逻辑,个人更倾向于使用责任链。责任链相更灵活,但不象状态机那人“概念集中”——从另一个方面说,比起乱用状态机,乱用责任链更容易让人抓狂和莫名其妙:)

昨天在一个技术会议上听到一个同事说,做业务开发的不需要考虑设计模式什么的。我当时笑了笑,没说话。实际自己心里清楚,即使是这样看似很土,很基本的线上录单系统,巧做与否还是很重要的。更何况其他千奇百怪的需求。接下来的工作,我会设法将这个框架做成一个DSL(Domain Specific Language),最后做到能使用XML或者其他形式的文本定义状态机结构,将真正涉及到具体业务实现的步骤作为状态机的一部方,用极少的Java代码实现。



0 0