设计模式-状态模式

设计模式-状态模式

状态模式其实某些业务系统中经常用到,比如某些报名审核系统,报名状态之间有比较复杂的转换,例如刚开始注册是待提交,提交后是待一级审核,此时可以进行退回、也可以选择审核通过/不通过,审核通过后还能进行上级审核等。

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

状态机中有三个核心元素:状态(State)、事件(Event)和动作(Action)。其中,事件也被称为转移条件,触发状态的转换和动作的执行,不过动作执行并不是必须的。

案例分析

有一个马里奥的游戏,初始时是最小的,期间可以吃蘑菇变成超级马里奥,获得斗篷变成斗篷马里奥等,不同的操作会对得分进行变更。

假设这个游戏只能有以上这些转换,不会死亡直到通过,有以下几种实现方式

分支逻辑法

这种方法是我们最常用的,定义一个枚举类包含可能出现的状态

java 复制代码
public enum State {
    SMALL(0),
    SUPER(1),
    FIRE(2),
    CAPE(3);
    private int value;

    private State(int value) {
        this.value = value;
    }

    public int getValue() {
        return this.value;
    }

}

定义一个状态机上下文,属性包括状态和得分,为每种事件定义一个处理方法:

java 复制代码
public class MarioStateMachine {
    
    private int score;
    private State currentState;

    public MarioStateMachine() {
        this.score = 0;
        this.currentState = State.SMALL;
    }

    public void obtainMushRoom() {
        if (State.SMALL.getValue() == currentState.getValue()) {
            score += 100;
            currentState = State.SUPER;
        }
    }

    public void obtainCape() {
        if (State.SMALL.getValue() == currentState.getValue()) {
            score += 200;
            currentState = State.CAPE;
            return;
        }
        if (State.SUPER.getValue() == currentState.getValue()) {
            score += 200;
            currentState = State.CAPE;
        }
    }

    public void obtainFireFlower() {
        if (State.SMALL.getValue() == currentState.getValue()) {
            score += 300;
            currentState = State.FIRE;
            return;
        }
        if (State.SUPER.getValue() == currentState.getValue()) {
            score += 300;
            currentState = State.FIRE;
        }
    }

    public void meetMonster() {
        if (State.SUPER.getValue() == currentState.getValue()) {
            score -= 100;
            currentState = State.SMALL;
            return;
        }
        if (State.CAPE.getValue() == currentState.getValue()) {
            score -= 200;
            currentState = State.SMALL;
            return;
        }
        if (State.FIRE.getValue() == currentState.getValue()) {
            score -= 300;
            currentState = State.SMALL;
        }
    }

    public int getScore() {
        return this.score;
    }

    public State getCurrentState() {
        return this.currentState;
    }

}

这种在转换简单的时候易于实现,但是在逻辑复杂时可能导致类代码过于庞大,新增或删除状态时需要查找有影响的地方并进行修改,在状态多且频繁变更的情况下难以维护。

查表法

案例事件触发的动作是非常简单的,只需要对分数进行对应变化即可,针对这类情况,我们可以定义两个二维数组,分别表示【原始状态】经过某个【事件】后最终的状态和需要变化的分数。

例如在 Small 状态下吃到蘑菇(Got MushRoom)就会变为 Super,得分 +100。

java 复制代码
// 定义事件
public enum Event {

    GOT_MUSHROOM(0),
    GOT_CAPE(1),
    GOT_FIRE(2),
    MET_MONSTER(3);

    private int value;

    private Event(int value) {
        this.value = value;
    }

    public int getValue() {
        return this.value;
    }

}
java 复制代码
public class MarioStateMachine {

    private int score;
    private State currentState;

    private static final State[][] transitionTable = {
            {State.SUPER, State.CAPE, State.FIRE, State.SMALL},
            {State.SUPER, State.CAPE, State.FIRE, State.SMALL},
            {State.CAPE, State.CAPE, State.CAPE, State.SMALL},
            {State.FIRE, State.FIRE, State.FIRE, State.SMALL}
    };

    private static final int[][] actionTable = {
            {+100, +200, +300, +0},
            {+0, +200, +300, -100},
            {+0, +0, +0, -200},
            {+0, +0, +0, -300}
    };

    public MarioStateMachine() {
        this.score = 0;
        this.currentState = State.SMALL;
    }

    public void obtainMushRoom() {
        executeEvent(Event.GOT_MUSHROOM);
    }

    public void obtainCape() {
        executeEvent(Event.GOT_CAPE);
    }

    public void obtainFireFlower() {
        executeEvent(Event.GOT_FIRE);
    }

    public void meetMonster() {
        executeEvent(Event.MET_MONSTER);
    }

    private void executeEvent(Event event) {
        int stateValue = currentState.getValue();
        int eventValue = event.getValue();
        this.currentState = transitionTable[stateValue][eventValue];
        this.score += actionTable[stateValue][eventValue];
    }

    public int getScore() {
        return this.score;
    }

    public State getCurrentState() {
        return this.currentState;
    }

}
状态模式

查表法是因为触发的动作非常简单,仅仅是对得分进行了修改,如果触发的动作非常复杂,例如需要更新表状态,生成待办等不方便用数组存储且逻辑依然耦合在大的处理类中。

利用状态模式我们定义一个接口,接口中包含可能遇到的所有事件

java 复制代码
public interface MarioStateService {

    void obtainMushRoom(MarioStateMachine stateMachine);

    void obtainCape(MarioStateMachine stateMachine);

    void obtainFireFlower(MarioStateMachine stateMachine);

    void meetMonster(MarioStateMachine stateMachine);

    State getState();

}

四种形态的马里奥都实现这个接口

java 复制代码
public class SmallMarioStateService implements MarioStateService {

    private static final SmallMarioStateService instance = new SmallMarioStateService();

    public static MarioStateService getInstance() {
        return instance;
    }

    @Override
    public void obtainMushRoom(MarioStateMachine stateMachine) {
        stateMachine.setCurrentStateService(SuperMarioStateService.getInstance());
        stateMachine.setScore(stateMachine.getScore() + 100);
    }

    @Override
    public void obtainCape(MarioStateMachine stateMachine) {
        stateMachine.setCurrentStateService(CapeMarioStateService.getInstance());
        stateMachine.setScore(stateMachine.getScore() + 200);
    }

    @Override
    public void obtainFireFlower(MarioStateMachine stateMachine) {
        stateMachine.setCurrentStateService(FireMarioStateService.getInstance());
        stateMachine.setScore(stateMachine.getScore() + 300);
    }

    @Override
    public void meetMonster(MarioStateMachine stateMachine) {

    }

    @Override
    public State getState() {
        return State.SMALL;
    }

}
java 复制代码
public class SuperMarioStateService implements MarioStateService {

    private static final SuperMarioStateService instance = new SuperMarioStateService();

    public static MarioStateService getInstance() {
        return instance;
    }

    @Override
    public void obtainMushRoom(MarioStateMachine stateMachine) {

    }

    @Override
    public void obtainCape(MarioStateMachine stateMachine) {
        stateMachine.setCurrentStateService(CapeMarioStateService.getInstance());
        stateMachine.setScore(stateMachine.getScore() + 200);
    }

    @Override
    public void obtainFireFlower(MarioStateMachine stateMachine) {
        stateMachine.setCurrentStateService(FireMarioStateService.getInstance());
        stateMachine.setScore(stateMachine.getScore() + 300);
    }

    @Override
    public void meetMonster(MarioStateMachine stateMachine) {
        stateMachine.setCurrentStateService(SmallMarioStateService.getInstance());
        stateMachine.setScore(stateMachine.getScore() - 100);
    }

    @Override
    public State getState() {
        return State.SUPER;
    }
}
java 复制代码
public class CapeMarioStateService implements MarioStateService {

    private static final CapeMarioStateService instance = new CapeMarioStateService();

    public static MarioStateService getInstance() {
        return instance;
    }

    @Override
    public void obtainMushRoom(MarioStateMachine stateMachine) {

    }

    @Override
    public void obtainCape(MarioStateMachine stateMachine) {

    }

    @Override
    public void obtainFireFlower(MarioStateMachine stateMachine) {

    }

    @Override
    public void meetMonster(MarioStateMachine stateMachine) {
        stateMachine.setCurrentStateService(SmallMarioStateService.getInstance());
        stateMachine.setScore(stateMachine.getScore() - 200);
    }

    @Override
    public State getState() {
        return State.CAPE;
    }

}
java 复制代码
public class FireMarioStateService implements MarioStateService {

    private static final FireMarioStateService instance = new FireMarioStateService();

    public static MarioStateService getInstance() {
        return instance;
    }

    @Override
    public void obtainMushRoom(MarioStateMachine stateMachine) {

    }

    @Override
    public void obtainCape(MarioStateMachine stateMachine) {

    }

    @Override
    public void obtainFireFlower(MarioStateMachine stateMachine) {

    }

    @Override
    public void meetMonster(MarioStateMachine stateMachine) {
        stateMachine.setCurrentStateService(SmallMarioStateService.getInstance());
        stateMachine.setScore(stateMachine.getScore() - 300);
    }

    @Override
    public State getState() {
        return State.FIRE;
    }
}

定义状态机类

java 复制代码
public class MarioStateMachine {

    @Getter
    @Setter
    private int score;

    @Getter
    @Setter
    private MarioStateService currentStateService;

    public MarioStateMachine() {
        this.score = 0;
        this.currentStateService = SmallMarioStateService.getInstance();
    }

    public void obtainMushRoom() {
        currentStateService.obtainMushRoom(this);
    }

    public void obtainCape() {
        currentStateService.obtainCape(this);
    }

    public void obtainFireFlower() {
        currentStateService.obtainFireFlower(this);
    }

    public void meetMonster() {
        currentStateService.meetMonster(this);
    }

    public State getState() {
        return currentStateService.getState();
    }

}

利用多态特性,使用接口 MarioStateService 存储当前真正的状态对象,并在遇到事件时调用当前真正对象的处理方法,并将状态对象设计为无状态的单例,多个状态机实例可以复用同一个状态对象。

不过该实现不够优雅,原因是按照目前的规则,例如 Fire(火焰形态)只会遇到一种事件就是遇到怪物,但是它依然需要实现所有的方法,该怎么设计解决这个问题呢?

第一种方案可以将给接口设计为 default 默认空实现,由实现类主动选择可能遇到的事件并进行重写。

第二种方案可以给接口一个抽象实现类,getState() 方法设计为抽象方法要求子类必须实现,其余接口全部空实现,由继承者主要选择并重写,其实和第一种方案思路一致。

第三种方案可以根据情况设计相应的实现类,例如 Fire 形态和 Cape 形态都只会处理遇到怪物事件,定义一个类实现接口并给其他接口空实现,FireImpl 和 CapeImpl 类继承这个实现类。

相关推荐
Hello.Reader3 小时前
Flink 状态模式演进(State Schema Evolution)从原理到落地的一站式指南
python·flink·状态模式
破晓之翼4 小时前
控制论的定义、工程意义及系统功能整合
设计模式·软件工程·产品经理·学习方法
bkspiderx5 小时前
C++设计模式之行为型模式:状态模式(State)
c++·设计模式·状态模式
rongqing201916 小时前
Google 智能体设计模式:人机协同(HITL)
设计模式
王嘉俊92517 小时前
设计模式--享元模式:优化内存使用的轻量级设计
java·设计模式·享元模式
bkspiderx19 小时前
C++设计模式之行为型模式:中介者模式(Mediator)
c++·设计模式·中介者模式
Meteors.1 天前
23种设计模式——责任链模式(Chain of Responsibility Pattern)
设计模式·责任链模式
o0向阳而生0o1 天前
107、23种设计模式之观察者模式(16/23)
观察者模式·设计模式
默默coding的程序猿1 天前
1.单例模式有哪几种常见的实现方式?
java·开发语言·spring boot·spring·单例模式·设计模式·idea