关于express:赶走烦人的ifelse使用状态模式推动业务生命周期的流转

2次阅读

共计 19523 个字符,预计需要花费 49 分钟才能阅读完成。

1. 业务背景
本文借助海内互金业务的借款流程开展。业务外围是借款的生命周期,相当于是电商中的订单一样。一笔借款的整个生命周期蕴含了提交,审批,确认,放款,还款。一笔借款的状态对应已上的操作,同样就很多了。如图是一笔借款的生命周期:

对于这么多状态,业务代码上有很多判断分支,什么状况下能够做什么操作,这是强校验的。业务初期疾速上线,if else 能够疾速地将业务串联起来,在各个业务解决的节点判断并执行业务逻辑。
 if (LoanStatusConstant.CREATED.equals(oldStatus) ||
     LoanStatusConstant.NEED_MORE_INFO.equals(oldStatus)) {
     log.info(“—> Loan to Submitted”);
 }
 ​
 if (LoanStatusConstant.APPROVED.equals(loan.getStatus())) {
     log.info(“—> Loan confirmed”);
 }
 ​
 if (!LoanStatusConstant.APPROVED.equals(loanStatus)) {
     log.info(“—> Loan approved,to Fund”);
 }
 //…….
复制代码
2. 业务代码的“坏滋味”
随着经营推广的力度加大,用户一拥而上,风控环境更加严苛,整个产品线也陆续退出了更多的流程去迭代产品,让危险和收益能趋于均衡。
这时候在开发代码上的体现就是代码库急剧收缩,业务扩张天然会扩招,新共事也会在已有的代码上打补丁,在这些补丁式的需要下,已经的 if else 会指数级的凌乱,一个简略的需要都可能挑战现有的状态分支。这种“坏滋味”不加以干涉,整个我的项目的生命力间接走向晚年。
 // 审核
 public void approve(@RequestBody LoanSubmitDTO submitDTO) {
     final Loan loan = loanMapper.selectOne(new LambdaQueryWrapper<Loan>().eq(Loan::getRefId, submitDTO.getLoanRefId()));
     if (Objects.isNull(loan)){
         throw new BaseBizException(“loan Not exists”);
    }
     if (!SUBMITTED.getCode().equals(loan.getStatus())){
         throw new BaseBizException(“loan status incorrect”);
    }
 ​
     loan.setStatus(APPROVED.getCode());
     loanMapper.updateById(loan);
 }
 // 确认
 public LoanDTO submit(LoanSubmitDTO submitDTO) {
     final Loan loan = loanMapper.selectOne(new LambdaQueryWrapper<Loan>().eq(Loan::getRefId, submitDTO.getLoanRefId()));
     if (Objects.isNull(loan)){
         throw new BaseBizException(“loan Not exists”);
    }
     if (!CREATED.getCode().equals(loan.getStatus())){
         throw new BaseBizException(“loan status incorrect”);
    }
 ​
     loan.setStatus(SUBMITTED.getCode());
     loanMapper.updateById(loan);
     riskService.callRisk(submitDTO);
 ​
     return BeanConvertUtil.map(loan,LoanDTO.class);
 }
复制代码
随着我的项目一直收缩,为了对贷款状态进行校验,if else 会充斥业务层的各个中央,此时,一旦产品上对业务流程进行调整,状态也会随着批改。比方新增了风控确认机制, 用户能够补充信息再提交,对于满足肯定条件的贷款能够让用户补充肯定的风控信息再提交,那么此时对于借款提交流程的前置状态就要发生变化了:
 // 提交
 public LoanDTO submit(LoanSubmitDTO submitDTO) {
     final Loan loan = loanMapper.selectOne(new LambdaQueryWrapper<Loan>().eq(Loan::getRefId, submitDTO.getLoanRefId()));
     if (Objects.isNull(loan)){
         throw new BaseBizException(“loan Not exists”);
    }
     // 判断条件发生变化。
     if (!CREATED.getCode().equals(loan.getStatus())&&!NEED_MORE_INFO.getCode().equals(loan.getStatus())){
         throw new BaseBizException(“loan status incorrect”);
    }
 ​
     loan.setStatus(SUBMITTED.getCode());
     loanMapper.updateById(loan);
     riskService.callRisk(submitDTO);
 ​
     return BeanConvertUtil.map(loan,LoanDTO.class);
 }
复制代码
我的项目迭代过程中每一次上线前测试同学都会进行严格地测试。以上这种变动可能会批改多个中央的代码,测试同学就不得不进行大面积的回归测试,上线危险会大大增加;而咱们开发同学这种新逻辑上线就硬改原有代码的行为,违反了开闭准则,随着业务的迭代,我的项目代码的可读性会越来越差(if-else 越来越多,测试用例不清晰),可能很多人感觉就改了个判断语句没什么大不了的,但实际上很多生产事变都是因为这种频繁的小改变导致的。
如何去躲避已上这种“坏滋味”呢?
3.OCP 准则(凋谢关闭准则)
3.1 定义
咱们先来看看什么是【凋谢关闭准则】:

Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.
–Bertrand Meyer

翻译过去的意思是:一个软件实体(类、模块、函数等)应答扩大凋谢,但对批改关闭。也就是说当咱们设计一个模块,一个实体对象时,应该在不批改本身源代码的状况下,可能扩大新的行为。
以上这句话读完,不能批改和可能扩大是自圆其说的,如何能力实现并满足开闭准则呢?

抽象化是要害,咱们形象出共性的基类,并且通过基类衍生进去的实现类去实现新行为的扩大。【形象层放弃不变,实现层实现扩大】。

3.2 比照阐明
举个简略的例子来看,咱们要做领取,领取形式必须要反对微信领取,支付宝领取等多种领取形式。此时咱们的类设计如下:

此时产品要接入新的领取形式,要反对银联领取,咱们就不得不去批改 switch 语句块的逻辑,新增银联领取形式,并新增银联领取的 channel 类。这就毁坏了开闭准则。其实开闭准则是咱们做面向对象开发的根底准则,所有导致原有代码批改的行为都会毁坏开闭准则。

这就考验咱们在对象设计时的要做到敏锐性和合理性,及时发现要害畛域中具备雷同行为的对象,应用抽象类或者接口,将雷同的行为形象到抽象类或者接口中,将不同的行为封装到子类实现类下面。
这样解决之后,零碎须要扩大性能时,咱们只有扩大新的子类就能够。对于子类的批改咱们也能够从新实现一个新的子类。

比方,咱们把所有的领取渠道形象出对立的领取行为,并且针对不同的领取渠道去扩大不同的子类,并通过工厂模式去治理所有的领取渠道,把领取渠道的抉择逻辑也封装到工厂中去,此时对于 PayService 这类业务对象来说,就不会因为领取形式的批改而去变动代码。这样就做到了,对扩大凋谢,对对更改敞开。
此时的类设计如下:

4. 状态模式
4.1 定义

Allow an object to alter its behavior when its internal state changes.The object will appear to change its class.
  ——《设计模式:可复用面向对象软件的根底》

状态模式(State Pattern):状态模式是一个行为型设计模式。容许一个对象在其外部状态扭转时扭转它的行为。该对象将看起来扭转了它的类。
状态模式的应用场景:用于解决零碎中简单对象的状态转换以及不同状态下行为的封装问题。对有状态的对象,把简单多样的状态从对象中抽离进去,封装到专门的状态类中,这样就能够让对象的状态灵便变动。
4.2 UML 图
状态模式的参与者:

环境类 Context 角色:能够认为是状态上下文,外部保护了对象以后的 ConcreteState,对客户方提供接口,能够负责具体状态的切换。
形象状态 State 角色:这是一个接口,用来封装环境类对象中的一个特定状态相干的行为。
具体状态 ConcreteState 角色:是 State 的子类实现类,具体状态子类,每一个子类都封装了一个 ConcreteState 相干的行为。

4.3 简略示例

bytemd-mermaid-1679302497423-0{font-family:”trebuchet ms”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#bytemd-mermaid-1679302497423-0 .error-icon{fill:#552222;}#bytemd-mermaid-1679302497423-0 .error-text{fill:#552222;stroke:#552222;}#bytemd-mermaid-1679302497423-0 .edge-thickness-normal{stroke-width:2px;}#bytemd-mermaid-1679302497423-0 .edge-thickness-thick{stroke-width:3.5px;}#bytemd-mermaid-1679302497423-0 .edge-pattern-solid{stroke-dasharray:0;}#bytemd-mermaid-1679302497423-0 .edge-pattern-dashed{stroke-dasharray:3;}#bytemd-mermaid-1679302497423-0 .edge-pattern-dotted{stroke-dasharray:2;}#bytemd-mermaid-1679302497423-0 .marker{fill:#333333;stroke:#333333;}#bytemd-mermaid-1679302497423-0 .marker.cross{stroke:#333333;}#bytemd-mermaid-1679302497423-0 svg{font-family:”trebuchet ms”,verdana,arial,sans-serif;font-size:16px;}#bytemd-mermaid-1679302497423-0 g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#bytemd-mermaid-1679302497423-0 g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#bytemd-mermaid-1679302497423-0 g.stateGroup .state-title{font-weight:bolder;fill:black;}#bytemd-mermaid-1679302497423-0 g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#bytemd-mermaid-1679302497423-0 g.stateGroup line{stroke:#333333;stroke-width:1;}#bytemd-mermaid-1679302497423-0 .transition{stroke:#333333;stroke-width:1;fill:none;}#bytemd-mermaid-1679302497423-0 .stateGroup .composit{fill:white;border-bottom:1px;}#bytemd-mermaid-1679302497423-0 .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#bytemd-mermaid-1679302497423-0 .state-note{stroke:#aaaa33;fill:#fff5ad;}#bytemd-mermaid-1679302497423-0 .state-note text{fill:black;stroke:none;font-size:10px;}#bytemd-mermaid-1679302497423-0 .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#bytemd-mermaid-1679302497423-0 .edgeLabel .label rect{fill:hsl(80,100%,96.2745098039%);opacity:0.5;}#bytemd-mermaid-1679302497423-0 .edgeLabel .label text{fill:rgb(6.3333333334,0,19.0000000001);}#bytemd-mermaid-1679302497423-0 .label div .edgeLabel{color:rgb(6.3333333334,0,19.0000000001);}#bytemd-mermaid-1679302497423-0 .stateLabel text{fill:black;font-size:10px;font-weight:bold;}#bytemd-mermaid-1679302497423-0 .node circle.state-start{fill:#333333;stroke:black;}#bytemd-mermaid-1679302497423-0 .node circle.state-end{fill:hsl(240,60%,86.2745098039%);stroke:white;stroke-width:1.5;}#bytemd-mermaid-1679302497423-0 .end-state-inner{fill:white;stroke-width:1.5;}#bytemd-mermaid-1679302497423-0 .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#bytemd-mermaid-1679302497423-0 #statediagram-barbEnd{fill:#333333;}#bytemd-mermaid-1679302497423-0 .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#bytemd-mermaid-1679302497423-0 .cluster-label,#bytemd-mermaid-1679302497423-0 .nodeLabel{color:#333;}#bytemd-mermaid-1679302497423-0 .statediagram-cluster rect.outer{rx:5px;ry:5px;}#bytemd-mermaid-1679302497423-0 .statediagram-state .divider{stroke:#9370DB;}#bytemd-mermaid-1679302497423-0 .statediagram-state .title-state{rx:5px;ry:5px;}#bytemd-mermaid-1679302497423-0 .statediagram-cluster.statediagram-cluster .inner{fill:white;}#bytemd-mermaid-1679302497423-0 .statediagram-cluster.statediagram-cluster-alt .inner{fill:#e0e0e0;}#bytemd-mermaid-1679302497423-0 .statediagram-cluster .inner{rx:0;ry:0;}#bytemd-mermaid-1679302497423-0 .statediagram-state rect.basic{rx:5px;ry:5px;}#bytemd-mermaid-1679302497423-0 .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#efefef;}#bytemd-mermaid-1679302497423-0 .note-edge{stroke-dasharray:5;}#bytemd-mermaid-1679302497423-0 .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#bytemd-mermaid-1679302497423-0 .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#bytemd-mermaid-1679302497423-0 .statediagram-note text{fill:black;}#bytemd-mermaid-1679302497423-0 .statediagram-note .nodeLabel{color:black;}#bytemd-mermaid-1679302497423-0 #dependencyStart,#bytemd-mermaid-1679302497423-0 #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#bytemd-mermaid-1679302497423-0:root{–mermaid-font-family:”trebuchet ms”,verdana,arial,sans-serif;}#bytemd-mermaid-1679302497423-0 state{fill:apa;}ABCX

形象状态类:
 public abstract class State {
 ​
     protected void a2b(Context context) {
         throw new BaseBizException(context.getState().getClass().getSimpleName()+” not support this transition to B state” );
    }
 ​
     protected void b2c(Context context) {
         throw new BaseBizException(context.getState().getClass().getSimpleName()+” not support this transition to C state” );
    }
 ​
     protected void a2c(Context context) {
         throw new BaseBizException(“current state is “+context.getState().getClass().getSimpleName()+” not support transition” );
    }
 }
复制代码
具体状态类:
 public class AState extends State {
 ​
 ​
     @Override
     protected void a2b(Context context) {
         System.out.println(“ 状态流转:a State ->b State”);
         context.setState(new BState());
    }
 ​
     @Override
     protected void a2c(Context context) {
         System.out.println(“ 状态流转:a State ->c State”);
         context.setState(new BState());
    }
 }
 ​
 public class BState extends State {
 ​
     @Override
     protected void b2c(Context context) {
         System.out.println(“ 状态流转:b State ->c State”);
         context.setState(new CState());
    }
 }
 public class CState extends State {
 ​
 }
复制代码
环境上下文类:
 public class Context {
     private State state;
 ​
     public Context(State initState) {
         this.state = initState;
    }
     public State getState() {
         return state;
    }
     public void setState(State state) {
         this.state = state;
    }
     public void a2b() {
         state.a2b(this);
    }
     public void b2c() {
         state.b2c(this);
    }
     public void a2c() {
         state.a2c(this);
    }
 }
复制代码
测试类:
 public class ClientInvoker {
 ​
     public static void main(String[] args) {
         Context context = new Context(StateFactory.getState(AState.class.getSimpleName()));
         context.a2b();
         context.a2c();
    }
 }
复制代码
测试后果:
 状态流转:a State ->b State
 Exception in thread “main” cn.ev.common.Exception.BaseBizException: current state is BState not support transition
  at state.State.a2c(State.java:16)
  at state.Context.a2c(Context.java:22)
  at state.ClientInvoker.main(ClientInvoker.java:8)
复制代码
4.4 状态模式的应用场景和优缺点
应用场景:

对象有多个状态,并且不同状态须要解决不同的行为。
对象须要依据本身变量的以后值扭转行为,不冀望应用大量 if-else 语句。
对于某些确定的状态和行为,不想应用反复代码。

长处:

合乎开闭准则,能够不便地扩大新的状态和对应的行为,只须要扭转对象状态即可扭转对象的行为。
状态转换逻辑与状态对象合成一体,防止了大量的分支判断语句和超大的条件语句块。

毛病:

一个状态一个子类,减少了零碎类和对象的个数。如果使用不当将导致程序结构和代码的凌乱。
肯定水平上满足了开闭准则,不过对于管制状态流转的职责类,增加新的状态类须要批改。

5. 优化借款流程
5.1 形象状态类
首先咱们定义形象状态类 AbstractLoanState:
 public  abstract class AbstractLoanState {
 ​
     @Resource
     protected LoanMapper loanMapper;
     /**
      * 获取 State
      * @return
      */
     abstract Integer getState();
     protected Loan getLoanDTO(String loanRefId) {
         final Loan loan = loanMapper.selectOne(new LambdaQueryWrapper<Loan>().eq(Loan::getRefId, loanRefId));
         if (Objects.isNull(loan)){
             throw new BaseBizException(“loan Not exists”);
        }else {
 ​
             return loan;
        }
    }
     /**
      * 借款提交
      * @param loanRefId 借款 id
      * @param currentState 以后状态
      * @return
      */
     protected  LoanDTO submit(String loanRefId, Enum<LoanStateEnum> currentState) {
         throw new BaseBizException(“ 状态不正确,请勿操作 ”);
    }
     /**
      * 借款审批通过
      * @param loanRefId 借款 id
      * @param currentState 以后状态
      * @return
      */
     protected LoanDTO approve(String loanRefId, Enum<LoanStateEnum> currentState) {
         throw new BaseBizException(“ 状态不正确,请勿操作 ”);
    }
     /**
      * 借款确认
      * @param loanRefId 借款 id
      * @param currentState 以后状态
      * @return
      */
     protected LoanDTO confirm(String loanRefId, Enum<LoanStateEnum> currentState){
         throw new BaseBizException(“ 状态不正确,请勿操作 ”);
    }
     /**
      * 借款审批回绝
      * @param loanRefId 借款 id
      * @param currentState 以后状态
      * @return
      */
     protected LoanDTO reject(String loanRefId, Enum<LoanStateEnum> currentState){
         throw new BaseBizException(“ 状态不正确,请勿操作 ”);
    }
     /**
      * 放款
      * @param loanRefId 借款 id
      * @param currentState 以后状态
      * @return
      */
     protected LoanDTO tofund(String loanRefId, Enum<LoanStateEnum> currentState) {
         throw new BaseBizException(“ 状态不正确,请勿操作 ”);
    }
     /**
      * 还款
      * @param loanRefId 借款 id
      * @param currentState 以后状态
      * @return
      */
     protected LoanDTO repay(String loanRefId, Enum<LoanStateEnum> currentState) {
         throw new BaseBizException(“ 状态不正确,请勿操作 ”);
    }
     /**
      * 补充信息
      * @param loanRefId 借款 id
      * @param currentState 以后状态
      * @return
      */
     protected LoanDTO needMoreInfo(String loanRefId, Enum<LoanStateEnum> currentState) {
         throw new BaseBizException(“ 状态不正确,请勿操作 ”);
    }
 }
复制代码
5.2 具体状态类
定义具体的状态类,每种不同的状态咱们都顺次定义专属的状态类,并赋予它特有的行为。例如 LoanSubmitState:
 @Component
 @Slf4j
 public class LoanSubmitState extends AbstractLoanState{
 ​
     @Override
     Integer getState() {
         return SUBMITTED.getCode();
    }
 ​
     @Override
     protected LoanDTO approve(String loanRefId, Enum<LoanStateEnum> currentState) {
         final Loan loan = getLoanDTO(loanRefId);
         loan.setStatus(APPROVED.getCode());
         loanMapper.updateById(loan);
         log.info(“loan {} 从 {} 状态流转到 {}”,loanRefId,currentState,APPROVED);
         return BeanConvertUtil.map(loan,LoanDTO.class);
    }
 ​
     @Override
     protected LoanDTO reject(String loanRefId, Enum<LoanStateEnum> currentState) {
         final Loan loan = getLoanDTO(loanRefId);
         loan.setStatus(REJECTED.getCode());
         loanMapper.updateById(loan);
         log.info(“loan {} 从 {} 状态流转到 {}”,loanRefId,currentState,REJECTED);
         return BeanConvertUtil.map(loan,LoanDTO.class);
    }
 ​
     @Override
     protected LoanDTO needMoreInfo(String loanRefId, Enum<LoanStateEnum> currentState) {
         final Loan loan = getLoanDTO(loanRefId);
         loan.setStatus(NEED_MORE_INFO.getCode());
         loanMapper.updateById(loan);
         log.info(“loan {} 从 {} 状态流转到 {}”,loanRefId,currentState,NEED_MORE_INFO);
         return BeanConvertUtil.map(loan,LoanDTO.class);
    }
 }
复制代码
LoanConfirmState,通过这些独立的状态类,咱们能够做到防止写大块的 if-else 语句,防止在业务的多个角落去保护这些分支语句。
 @Component
 @Slf4j
 public class LoanConfirmState extends AbstractLoanState{
     @Resource
     private FundService fundService;
 ​
     @Override
     Integer getState() {
         return CONFIRMED.getCode();
    }
 ​
     @Override
     protected LoanDTO tofund(String loanRefId, Enum<LoanStateEnum> currentState) {
         final Loan loan = getLoanDTO(loanRefId);
         loan.setStatus(FUNDED.getCode());
         loanMapper.updateById(loan);
         final LoanFundDTO loanFundDTO = BeanConvertUtil.map(loan, LoanFundDTO.class);
         fundService.sendMoney(loanFundDTO);
 ​
         log.info(“loan {} 从 {} 状态流转到 {}”,loanRefId,currentState,FUNDED);
         return BeanConvertUtil.map(loan,LoanDTO.class);
    }
 }
复制代码
5.3 环境上下文类
LoanStatusHandler 封装了借款生命周期中的所有操作接口,对于内部客户调用方只须要和他交互就能够对借款状态进行流转,齐全不须要和具体的状态类进行耦合,这样对于前期的状态类扩大就很不便了。
 @Service
 public class LoanStatusHandler {
 ​
 ​
     @Resource
     protected LoanMapper loanMapper;
     public Loan getLoan(String loanRefId) {
         final Loan loan = loanMapper.selectOne(new LambdaQueryWrapper<Loan>().eq(Loan::getRefId, loanRefId));
         if (Objects.isNull(loan)){
             throw new BaseBizException(“loan Not exists”);
        }else {
 ​
             return loan;
        }
    }
     /**
      * 借款提交
      * @param loanRefId 借款 id
      * @return
      */
     public  LoanDTO submit(String loanRefId) {
         final Loan loan = getLoan(loanRefId);
         final AbstractLoanState currentState = LoanStateFactory.chooseLoanState(loan.getStatus());
         return currentState.submit(loanRefId,LoanStateEnum.getEnumByCode(loan.getStatus()));
    }
     /**
      * 借款审批通过
      * @param loanRefId 借款 id
      * @return
      */
     public LoanDTO approve(String loanRefId) {
         final Loan loan = getLoan(loanRefId);
         final AbstractLoanState currentState = LoanStateFactory.chooseLoanState(loan.getStatus());
         return currentState.approve(loanRefId,LoanStateEnum.getEnumByCode(loan.getStatus()));
    }
     /**
      * 借款确认
      * @param loanRefId 借款 id
      * @return
      */
     public LoanDTO confirm(String loanRefId){
         final Loan loan = getLoan(loanRefId);
         final AbstractLoanState currentState = LoanStateFactory.chooseLoanState(loan.getStatus());
         return currentState.confirm(loanRefId,LoanStateEnum.getEnumByCode(loan.getStatus()));
    }
     /**
      * 借款审批回绝
      * @param loanRefId 借款 id
      * @return
      */
     public LoanDTO reject(String loanRefId){
         final Loan loan = getLoan(loanRefId);
         final AbstractLoanState currentState = LoanStateFactory.chooseLoanState(loan.getStatus());
         return currentState.reject(loanRefId,LoanStateEnum.getEnumByCode(loan.getStatus()));
    }
     /**
      * 放款
      * @param loanRefId 借款 id
      * @return
      */
     public LoanDTO tofund(String loanRefId) {
         final Loan loan = getLoan(loanRefId);
         final AbstractLoanState currentState = LoanStateFactory.chooseLoanState(loan.getStatus());
         return currentState.tofund(loanRefId,LoanStateEnum.getEnumByCode(loan.getStatus()));
    }
 ​
     /**
      * 还款
      * @param loanRefId 借款 id
      * @return
      */
     public LoanDTO repay(String loanRefId) {
         final Loan loan = getLoan(loanRefId);
         final AbstractLoanState currentState = LoanStateFactory.chooseLoanState(loan.getStatus());
         return currentState.repay(loanRefId,LoanStateEnum.getEnumByCode(loan.getStatus()));
    }
     /**
      * 补充信息
      * @param loanRefId 借款 id
      * @return
      */
     public LoanDTO needMoreInfo(String loanRefId) {
         final Loan loan = getLoan(loanRefId);
         final AbstractLoanState currentState = LoanStateFactory.chooseLoanState(loan.getStatus());
         return currentState.needMoreInfo(loanRefId,LoanStateEnum.getEnumByCode(loan.getStatus()));
    }
 }
复制代码
5.4 状态工厂类
封装了一个持有所有状态实例的工厂,这样就能够依据借款的状态去获取单例的状态类,既不节约内存,共享了所有的状态实例,也能够很好地联合了借款畛域对象的状态去推动咱们封装的状态模式流转业务。
 @Component
 public class LoanStateFactory implements ApplicationContextAware {
 ​
     private final static Map<Integer, AbstractLoanState> loanStateMap =new LinkedHashMap<>();
     @Override
     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
         Map<String, AbstractLoanState> map = applicationContext.getBeansOfType(AbstractLoanState.class);
         map.forEach((key,value)->loanStateMap.put(value.getState(),value));
    }
 ​
     public static AbstractLoanState chooseLoanState(Integer currentState){
         return loanStateMap.get(currentState);
    }
 }
复制代码
5.5 单元测试
首先创立一笔借款,而后顺次通过 LoanStatusHandler 状态解决类去推动借款的生命周期,如果状态变动能够从 Created–>PaidOff,那么就阐明状态模式的流转没有问题,测试通过。
 @Test
 public void testStateCreatedToPaidOff() {
     LoanCreateDTO loanCreateDTO = createLoan();
 ​
     LoanDTO loanDTO = loanService.create(loanCreateDTO);
 ​
     String loanRefId = loanDTO.getRefId();
 ​
     loanStatusHandler.submit(loanRefId);
 ​
     loanStatusHandler.needMoreInfo(loanRefId);
 ​
     loanStatusHandler.submit(loanRefId);
 ​
     loanStatusHandler.approve(loanRefId);
 ​
     loanStatusHandler.confirm(loanRefId);
 ​
     loanStatusHandler.tofund(loanRefId);
     loanStatusHandler.repay(loanRefId);
 }
复制代码
5.6 测试后果
以下输入后果阐明了整个借款的正向流程的状态变动,单元测试通过。
 2023-03-17 17:35:14.907 [main] [INFO] c.e.p.t.fsm.ifElse.LoanService.create:52 [] – W3p6qS_loan loan created
 2023-03-17 17:35:15.230 [main] [INFO] c.e.p.t.fsm.ifElse.RiskService.callRisk:17 [] – null callRisk
 2023-03-17 17:35:15.231 [main] [INFO] c.e.p.t.f.s.LoanCreateState.submit:36 [] – loan W3p6qS_loan 从 CREATED 状态流转到 SUBMITTED,调用风控
 2023-03-17 17:35:15.370 [main] [INFO] c.e.p.t.f.s.LoanSubmitState.needMoreInfo:49 [] – loan W3p6qS_loan 从 SUBMITTED 状态流转到 NEED_MORE_INFO
 2023-03-17 17:35:15.501 [main] [INFO] c.e.p.t.fsm.ifElse.RiskService.callRisk:17 [] – null callRisk
 2023-03-17 17:35:15.501 [main] [INFO] c.e.p.t.f.s.LoanNeedMoreInfoState.submit:35 [] – loan W3p6qS_loan 从 NEED_MORE_INFO 状态流转到 SUBMITTED,调用风控
 2023-03-17 17:35:15.639 [main] [INFO] c.e.p.t.f.s.LoanSubmitState.approve:31 [] – loan W3p6qS_loan 从 SUBMITTED 状态流转到 APPROVED
 2023-03-17 17:35:15.772 [main] [INFO] c.e.p.t.f.s.LoanApproveState.confirm:30 [] – loan W3p6qS_loan 从 APPROVED 状态流转到 CONFIRMED
 2023-03-17 17:35:15.915 [main] [INFO] c.e.p.t.fsm.ifElse.FundService.sendMoney:17 [] – null sendMoney
 2023-03-17 17:35:15.915 [main] [INFO] c.e.p.t.f.s.LoanConfirmState.tofund:36 [] – loan W3p6qS_loan 从 CONFIRMED 状态流转到 FUNDED
 2023-03-17 17:35:16.051 [main] [INFO] c.e.p.t.f.ifElse.RepayService.rapay:17 [] – W3p6qS_loan sendMoney
 2023-03-17 17:35:16.051 [main] [INFO] c.e.p.t.f.s.LoanFundState.repay:36 [] – loan W3p6qS_loan 从 FUNDED 状态流转到 PAID_OFF
复制代码
6. 更多思考
理论我的项目中应用状态模式去革新业务流程会有这些状况产生:

扩大状态需减少状态类,状态多了会呈现很多状态类,随着状态的一直增多,导致形象状态类和上下文类中的办法定义可能会变得很多。
状态模式尽管让状态独立,通过定义新的子类很容易地减少新的状态和转换,较好的适应了开闭准则。然而并没有齐全实现状态与业务解耦。比方上文中具体状态类中还有对畛域对象的 DB 操作。

对于简单的业务状态流转,其实能够有一种优雅的实现办法:状态机。在 Java 我的项目中,比拟罕用的有 Spring Statemachine 和 Squirrel-foundation。通过状态机去推动业务流程,状态转移和业务逻辑基于事件齐全解耦,整个零碎状态能够更好地保护和扩大。

正文完
 0