状态模式

446 阅读6分钟

需求1 简单的审核流程

动作

prev next

状态

新建 待审核 审核通过 退回修改

BO

@Data
public class BO {
    private long id;
    private String name;
    private StatusEnum status;
}

StatusEnum

@Data
public enum StatusEnum {
    INIT(1, "新建"),
    WAITING(2, "待审核"),
    PASSED(3, "审核通过"),
    REJECT(4, "驳回修改");
}

if/else实现

// 下一步操作
next() { 
    if(status = "新建"){ status = "带审核"; return; } 
    if(status = "待审核"){ status = "审核通过"; return; } 
    if(status = "审核通过"){ throw new Excption("当前状态没有下一步操作") }
    if(status = "退回修改"){ status = "退回"; return; } 
}
// 上一步操作
prev() { 
    if(status = "新建"){ throw new Excption("当前状态没有上一步操作") }
    if(status = "待审核"){ status = "退回修改"; return; }
    if(status = "审核通过"){ throw new Excption("当前状态没有上一步操作") }
    if(status = "退回修改"){ throw new Excption("当前状态没有上一步操作") }
}
  • 优点:速度
  • 缺点:不灵活

状态迁移表实现

state Event new state
新建 prev none
新建 next 待审核
待审核 prev 退回修改
待审核 next 审核通过
审核通过 prev none
审核通过 next none
退回修改 prev none
退回修改 next 待审核
  • 优点:灵活,运行时改变状态机逻辑; 多个迁移表实现多种状态机逻辑
  • 缺点:读数据库

状态模式State

参与者

  • State接口
  • 状态实现类
  • Context (StateMachine.java)

按动作设计API

public interface DemoService {
    // 下一步流程
    String next(DTO reqDTO) throws PendingException;

    // 上一步流程
    String prev(DTO reqDTO) throws PendingException;
}

State接口

public interface State {

    void actionToPrev() throws PendingException;

    void actionToNext() throws PendingException;
}

State实现类

InitState PassedState RejectState WaitingState

/**
* 应用状态:等待审核
*/
@Service("WAITING_STATE")
public class WaitingState implements State {

    @Setter
    @Autowired
    private StateMachine stateMachine;

    /**
    * 审核不通过
    */
    @Override
    public void actionToPrev() throws PendingException {
        // Context获取当前的BO
        BO bo = stateMachine.getBO();
        // 更新状态
        bo.setStatus("退回修改");
    }

    /**
    * 审核通过
    */
    @Override
    public void actionToNext() throws PendingException {
        // Context获取当前的BO
        BO bo = stateMachine.getBO();
        // 更新状态
        bo.setStatus("审核通过");
        
        // 如果有需要, 可以通过Context状态转换, 继续执行新State下的action
        stateMachine.setBO(bo);
    }
}

StateMachine

@Data
@Service
public class StateMachine implements State {
    @Autowired
    private InitState initState;
    @Autowired
    private WaitingState waitingState;
    @Autowired
    private RejectState rejectState;
    @Autowired
    private PassedState passedState;

    private State state;
    
    @Getter
    private BO bo;

    @Override
    public void actionToPrev() throws PendingException {
        state.actionToPrev();
    }

    @Override
    public void actionToNext() throws PendingException {
        state.actionToNext();
    }

    // Context这里负责状态的转换: 当传入BO的时候,根据status选择State实现类
    public void setBO(BO bo) throws PendingException {
        this.bo = bo;
        StatusEnum status = this.bo.getStatus();
        if (status == "新建") {
            this.state = initState;
            return;
        }
        if (status == "审核通过") {
            this.state = passedState;
            return;
        }
        if ((status == "待审核") {
            this.state = waitingState;
            return;
        }
        if ((status == "退回修改") {
            this.state = rejectState;
            return;
        }
        throw new PendingException(MerCodeEnum.M1060);
    }

}

用法

public class Demo implements DemoService {
    @Autowired
    private BoDAO boDAO;
    @Autowired 
    private StateMachine stateMachine;

    @Override
    public void prev(PrevStateReqDTO reqDTO) throws PendingException {

       BO bo = boDAO.selectById(reqDTO.getId());

        // 状态的转换, 选择State实现类
       stateMachine.setBO(bo);
       
       // 执行动作
       stateMachine.actionToPrev();
       
        // 入库操作
        ......
    }

    @Override
    public void next(NextStateReqDTO reqDTO) throws PendingException {

       BO bo = boDAO.selectById(reqDTO.getId());
       
       // 状态的转换, 选择State实现类
       stateMachine.setBO(bo);
       
       // 执行动作
       stateMachine.actionToNext();
    }

}

谁定义状态的转换

1. 可以在Conetxt中定义转态转换, 即Conetxt需要注入所有State接口的实体类

见上面的示例中的StateMachine类的setBO()方法

2. 可以在State子类中指定后续转态转换,缺点:子类拥有其他子类的信息

State接口
public interface State {
    void updateStatus(BO bo) throws PendingException;
    
    void actionToPrev() throws PendingException;

    void actionToNext() throws PendingException;
}
StateMachine
@Data
@Service
public class StateMachine implements State {
    
    // 不需要注入所有的State实现类
    
    private State state;
    private BO bo;
    
    @Override
    public void updateStatus(BO bo) throws PendingException {
        // 状态转换
        state.updateStatus(bo);
    }
    
    @Override
    public void actionToPrev() throws PendingException {
        state.actionToPrev(bo);
    }

    @Override
    public void actionToNext(BO bo) throws PendingException {
        state.actionToNext(bo);
    }
}
WaitingState实现类
/**
* 应用状态:等待审核
*/
@Service("WAITING")
public class WaitingState implements State {

    @Setter
    @Autowired
    private StateMachine stateMachine;
    
    // 子类注入其他子类的信息
    @Autowired
    private RejectdState rejectdState;
    @Autowired
    private PassedState passedState;

    @Override
    public void updateStatus(BO bo) throws PendingException {
        // 状态转换
        bo.setState("等待审核");
    }
    
    /**
    * 审核不通过
    */
    @Override
    public void actionToPrev() throws PendingException {
        // 子类负责状态转换, 直接指定State的实现类
        stateMachine.setState(rejectdState);
        // 更新状态
        stateMachine.updateStatus();
    }

    /**
    * 审核通过
    */
    @Override
    public void actionToNext() throws PendingException {
        // 状态转换
        stateMachine.setState(passedState);
        // 更新状态
        stateMachine.updateStatus();
    }
}

状态模式优缺点

  • 优点:效率高 + 灵活
  • 缺点:类比较多
  • API设计风险点: 同一个人不小心多次调用prev接口, 会导致流程到达终态

优化代码v2 开闭原则

- 对修改关闭
不用修改已有代码
- 对扩展开放
只需新增一个状态类

StateMachine

@Data
@Service
public class StateMachine implements State {

    // @Autowired
    // private WaitingState waitingState;
    // @Autowired
    // private RejectState rejectState;
    // @Autowired
    // private PassedState passedState;
    
    @Autowired
    private Map<String, State> stateMap;
}

State实现类

@Service("...")用于指定stateMap中的key

@Service("WAITING_STATE")
public class WaitingState implements State {
    ......
}

@Service("PASSED_STATE")
public class PassedState implements State {
    ......
}

优化代码v3 并发场景

StateMachine原型模式

@Scope("prototype") 表示每次获得bean都会生成一个新的对象

@Data
@Service
@Scope("prototype")
public class StateMachine implements State {

    @Autowired
    private Map<String, State> stateMap;

    private State state;
    
    private BO bo;
    ......

}

StateMachine 单例模式

删除公共字段

@Data
@Service
public class StateMachine implements State {
    private final static String _SERVICE = "%s_STATE";
    
    @Autowired
    private Map<String, State> stateMap;

    @Override
    public void actionToPrev(BO bo) throws PendingException {
        // 当前状态
        StatusEnum status = bo.getStatus();
        stateMap.get(String.format(_SERVICE, status.name())).actionToPrev();
    }

    @Override
    public void actionToNext(BO bo) throws PendingException {
        // 当前状态
        StatusEnum status = bo.getStatus();
        stateMap.get(String.format(_SERVICE, status.name())).actionToNext();
    }

}

State接口

StateMachine是单例时, 由于没有缓存BO, 所以BO要作为参数传入State接口

public interface State {

    void actionToPrev(BO bo) throws PendingException;

    void actionToNext(BO bo) throws PendingException;
}

需求2 在审核通过后再加一个 停用 状态

ClosedState实现类

新增一个State实现类ClosedState

/**
* 应用状态:停用
*/
@Service("CLOSED")
public class ClosedState implements State {

    @Setter
    @Autowired
    private StateMachine stateMachine;

    /**
    * next
    */
    @Override
    public void actionToNext() throws PendingException {
         // Context获取当前的BO
        BO bo = stateMachine.getBO();
        // 更新状态
        bo.setStatus("待审核");
    }
}

修改StatusEnum

@Data
public enum StatusEnum {
    ......,
    CLOSED(5, "停用");
}
修改PassedState实现类
/**
* 应用状态:审核通过
*/
@Service("PASSED")
public class PassedState implements State {

    @Setter
    @Autowired
    private StateMachine stateMachine;

    /**
    * prev
    */
    @Override
    public void actionToPrev() throws PendingException {
        // Context获取当前的BO
        BO bo = stateMachine.getBO();
        // 更新状态
        bo.setStatus("停用");
    }
}

状态模式 VS 策略模式

  • 状态模式

  • 策略模式

需求3 一个动作下有多个状态

  • 需求1是一个动作没有分支:prev -> success
  • 需求2是一个动作存在分支:pay -> success | fail

按动作设计API

public interface DemoService {
    // 下单
    String build(DTO reqDTO) throws PendingException;

    // 支付
    // result: 成功 success 失败 fail
    String pay(DTO reqDTO, ResultEnum result) throws PendingException;
    
    // 担保
    // result: 成功 success 失败 fail
    String guarantee(DTO reqDTO, ResultEnum result) throws PendingException;
}

State接口

每个动作的大体的共同点是分为成功,失败

public interface State {
    // 成功
    String success() throws PendingException;
    // 失败
    String fail() throws PendingException;
    // 更新状态值
    String updateStatus() throws PendingException;
}

StateMachine

@Data
@Service
public class StateMachine implements State {
    private final static String _SERVICE = "%s_STATE";
    
    @Autowired
    private Map<String, State> stateMap;
    
    public void changeState(BO bo, ResultEnum result) throws PendingException {
        if (ResultEnum.SUCCESS.equals(result)) {
            this.success(bo);
            return;
        }
        if (ResultEnum.FAIL.equals(result)) {
            this.fail(bo);
            return;
        }
        throw new PendingException(TradeCode.T0010);
    }

    @Override
    public void updateStatus(BO bo) throws PendingException {
        // 状态转换
        stateMap.get(String.format(_SERVICE, bo.getStatus().name())).updateStatus(bo);
    }
    
    @Override
    public void success(BO bo) throws PendingException {
        stateMap.get(String.format(_SERVICE, bo.getStatus().name())).actionToPrev(bo);
    }

    @Override
    public void fail(BO bo) throws PendingException {
        stateMap.get(String.format(_SERVICE, bo.getStatus().name())).actionToNext(bo);
    }
}

用法

public class Demo impl DemoService {
    // 下单
    String build(DTO reqDTO) throws PendingException{
        stateMachine.sucess(bo);
    }

    // 支付
    // result: 成功 success 失败 fail
    String pay(DTO reqDTO, ResultEnum result) throws PendingException {
        // 根据result判断是执行success还是执行fail方法
        stateMachine.changeState(bo, result.name());
    }
}

需求4 双状态

降纬处理

  • API设计风险点: 同一个人不小心多次调用prev接口, 会导致流程到达终态