状态设计模式

状态模式

    • 一、状态模式概述
    • 二、状态模式的三种实现方式
      • 2.1 逻辑分支
      • 2.2 查表法
      • 2.3 状态模式
    • 三、三种方式的适用情况

一、状态模式概述

状态模式一般用来实现状态机,而状态机常用在游戏、工作流引擎等系统开发中。不过,状态机的实现方式有多种,除了状态模式,比较常用的还有分支逻辑法和查表法

有限状态机,英文翻译是 Finite State Machine,缩写为 FSM,简称为状态机。状态机有 3 个组成部分:状态(State)、事件(Event)、动作(Action)。其中,事件也称为转移条件(Transition Condition)。事件触发状态的转移及动作的执行。不过,动作不是必须的,也可能只转移状态,不执行任何动作。

二、状态模式的三种实现方式

以支付收获为例。

三种状态State:未支付、未收货、完成。

两种事件Event:支付、收货。

状态的流转即动作:支付后状态为未收货,收获后状态为完成。

/**
 * 支付状态枚举
 */
@Getter
public enum PayStateEnum {

    /**
     * 待支付
     */
    UNPAY(0),

    /**
     * 待收货
     */
    UNRECEIVE(1),

    /**
     * 完成
     */
    FINAL(2),

    /**
     * 错误
     */
    ERROR(3);

    /**
     * 值
     */
    private int value;

    PayStateEnum(int value) {
        this.value = value;
    }
}

/**
 * 支付事件枚举
 */
@Getter
public enum PayEventEnum {

    /**
     * 支付
     */
    PAY(0),

    /**
     * 收货
     */
    RECEIVE(1);

    /**
     * 值
     */
    private int value;

    PayEventEnum(int value) {
        this.value = value;
    }
}

2.1 逻辑分支

使用if-else或者switch-case进行处理的方式

/**
 * 支付状态机:逻辑分支
 */
@Slf4j
public class PayStateMachine {

    /**
     * 当前状态
     */
    private PayStateEnum payStateEnum;

    public PayStateMachine() {
        payStateEnum = PayStateEnum.UNPAY;
    }

    public void executeEvent(PayEventEnum payEventEnum) {
        switch (payEventEnum) {

            //支付行为发生:未支付->待收货
            case PAY:
                if (this.payStateEnum != PayStateEnum.UNPAY) {
                    log.info("商铺不是【未支付】状态,请核验!");
                    this.payStateEnum = PayStateEnum.ERROR;
                    break;
                }
                this.payStateEnum = PayStateEnum.UNRECEIVE;
                break;

            //收货行为发生:待收货->完成
            case RECEIVE:
                if (this.payStateEnum != PayStateEnum.UNRECEIVE) {
                    log.info("商铺不是【未收货】状态,请核验!");
                    this.payStateEnum = PayStateEnum.ERROR;
                    break;
                }
                this.payStateEnum = PayStateEnum.FINAL;
                break;
            default:
                log.info("未设置的行为!");
                break;
        }
    }

    public PayStateEnum getCurrentState() {
        return this.payStateEnum;
    }
}

/**
 * 测试逻辑分支方式
 */
@Test
public void testLogicBranch() {
    PayStateMachine payStateMachine = new PayStateMachine();
    log.info("当前状态为:{}", payStateMachine.getCurrentState());
    payStateMachine.executeEvent(PayEventEnum.RECEIVE);
    log.info("当前状态为:{}", payStateMachine.getCurrentState());
}

2.2 查表法

将状态和事件形成一个二维矩阵表,将结果态放入其中。

UNPAY UNRECEIVE FINAL
PAY 状态转为UNRECEIVE ERROR ERROR
RECEIVE ERROR 状态转为FINAL ERROR

我们可以将结果态转为一个二维数组进行存储,用的时候使用状态和事件枚举的value值作为索引,获取结果态。

/**
 * 支付状态机:查表法
 */
public class PayStateMachine2 {

    /**
     * 当前状态
     */
    private PayStateEnum currentPayStateEnum;

    /**
     * 将结果态转为一个二维数组进行存储
     */
    private static final PayStateEnum[][] payStateTable= {
        {UNRECEIVE, ERROR, ERROR},
        {ERROR, FINAL, ERROR}
    };

    /**
     * 初始化
     */
    public PayStateMachine2() {
        currentPayStateEnum = PayStateEnum.UNPAY;
    }

    /**
     * 用的时候使用状态和事件枚举的value值作为索引,获取结果态。
     */
    public void executeEvent(PayEventEnum payEventEnum) {
        this.currentPayStateEnum =
                payStateTable[this.currentPayStateEnum.getValue()]
                        [payEventEnum.getValue()];
    }

    /**
     * 获取当前状态
     */
    public PayStateEnum getCurrentState() {
        return this.currentPayStateEnum;
    }
}

/**
 * 测试查表法方式
 */
@Test
public void testCheckTable() {
    PayStateMachine2 payStateMachine = new PayStateMachine2();
    log.info("当前状态为:{}", payStateMachine.getCurrentState());
    payStateMachine.executeEvent(PayEventEnum.PAY);
    log.info("当前状态为:{}", payStateMachine.getCurrentState());
    payStateMachine.executeEvent(PayEventEnum.RECEIVE);
    log.info("当前状态为:{}", payStateMachine.getCurrentState());
}

2.3 状态模式

状态模式,定义状态接口,并将行为规定为抽象方法,实现对应三种状态以及行为方法(实现状态流转逻辑)。定义状态机,并依赖状态接口。同时,每个具体的状态又依赖状态机。(即状态机和各个状态类之间是双向依赖关系,因为每个状态需要依靠状态机修改状态)。
状态设计模式_第1张图片
这里要注意,具体的状态实现类Unpay和UnReceive都是固定的表示一种状态,不能修改,所以最好使用单例。

定义状态和实现状态。

/**
 * 状态接口
 */
public interface IPay {

    /**
     * 获取当前状态
     */
    PayStateEnum getState();

    /**
     * 支付行为
     */
    void pay(PayStateMachine3 payStateMachine);

    /**
     * 收货行为
     */
    void receive(PayStateMachine3 payStateMachine);
}

/**
 * 未支付状态
 */
@Slf4j
public class Unpay implements IPay {

    /**
     * 单例模式
     */
    private static final Unpay INSTANCE = new Unpay();

    @Override
    public PayStateEnum getState() {
        return PayStateEnum.UNPAY;
    }
	
    //依赖状态机修改状态
    @Override
    public void pay(PayStateMachine3 payStateMachine) {
        payStateMachine.setIPay(UnReceive.getInstance()); //将未收货状态类注入
    }

    @Override
    public void receive(PayStateMachine3 payStateMachine) {
        log.info("未支付,不能收货!");
    }

    /**
     * 单例:提供获取实例方法
     */
    public static Unpay getInstance() {
        return INSTANCE;
    }
}

/**
 * 未收货
 */
@Slf4j
public class UnReceive implements IPay {

    /**
     * 单例模式
     */
    private static final UnReceive INSTANCE = new UnReceive();

    @Override
    public PayStateEnum getState() {
        return PayStateEnum.UNRECEIVE;
    }

    @Override
    public void pay(PayStateMachine3 payStateMachine) {
        log.info("未收到货,不能支付");
    }

    @Override
    public void receive(PayStateMachine3 payStateMachine) {
        payStateMachine.setIPay(Final.getInstance());
    }

    public static UnReceive getInstance() {
        return INSTANCE;
    }
}

/**
 * 完成
 */
@Slf4j
public class Final implements IPay {

    /**
     * 单例模式
     */
    private static final Final INSTANCE = new Final();

    @Override
    public PayStateEnum getState() {
        return PayStateEnum.FINAL;
    }

    @Override
    public void pay(PayStateMachine3 payStateMachine) {
        log.info("订单已完成,不能支付!" );
    }

    @Override
    public void receive(PayStateMachine3 payStateMachine) {
        log.info("订单已完成,不能收货!" );
    }

    public static Final getInstance() {
        return INSTANCE;
    }
}

定义状态机。

/**
 * 状态模式
 */
@Setter
public class PayStateMachine3 {

    /**
     * 将状态接口注入
     */
    private IPay iPay;

    /**
     * 初始化:未支付
     */
    public PayStateMachine3() {
        this.iPay = Unpay.getInstance();
    }

    /**
     * 支付
     */
    public void pay() {
        this.iPay.pay(this);
    }

    /**
     * 收货
     */
    public void receive() {
        this.iPay.receive(this);
    }

    /**
     * 获取当前状态:通过状态的具体实现来修改掉内部的iPay实现(多态)
     */
    public PayStateEnum getCurrentState() {
        return this.iPay.getState();
    }
}

/**
 * 测试状态模式
 */
@Test
public void testStateDesign() {
    PayStateMachine3 payStateMachine = new PayStateMachine3();
    log.info("当前状态为:{}", payStateMachine.getCurrentState());
    payStateMachine.pay();
    log.info("当前状态为:{}", payStateMachine.getCurrentState());
    payStateMachine.receive();
    log.info("当前状态为:{}", payStateMachine.getCurrentState());
}

三、三种方式的适用情况

实际上,像游戏这种比较复杂的状态机,包含的状态比较多,我优先推荐使用查表法,而状态模式会引入非常多的状态类,会导致代码比较难维护。相反,像电商下单、外卖下单这种类型的状态机,它们的状态并不多,状态转移也比较简单,但事件触发执行的动作包含的业务逻辑可能会比较复杂,所以,更加推荐使用状态模式来实现

你可能感兴趣的:(Design,Patterns,设计模式,java,状态设计模式,状态模式)