设计模式之美25--状态模式

状态机的实现方式有多种,除了状态模式,比较常用的还有 分支逻辑法 和 查表法。

状态机又叫有限状态机,它有3个部分组成:状态、事件、动作。
超级玛丽示例:
在这里插入图片描述
1, 分之逻辑法

class MarioStateMachine {
    private int score;
    private State currentState;

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

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

    public void obtainCape() {
        if (currentState.equals(State.SMALL) || currentState.equals(State.SUPER)) {
            this.currentState = State.CAPE;
            this.score += 200;
        }
    }

    public void obtainFireFlower() {
        if (currentState.equals(State.SMALL) || currentState.equals(State.SUPER)) {
            this.currentState = State.FIRE;
            this.score += 300;
        }
    }

    public void meetMonster() {
        if (currentState.equals(State.SUPER)) {
            this.currentState = State.SMALL;
            this.score -= 100;
            return;
        }
        if(currentState.equals(State.CAPE)) {
            this.currentState = State.SMALL;
            this.score -= 200;
            return;
        }
        if(currentState.equals(State.FIRE)) {
            this.currentState = State.SMALL;
            this.score -= 300;
            return;
        }
    }
    public int getScore() {
        return this.score;
    } 
    public State getCurrentState() {
        return this.currentState;
    }
}

2, 查表法
上面这种实现方法有点类似hard code,对于复杂的状态机来说不适用,而状态机的第二种实现方式查表法,就更加合适了。接下
来,我们就一块儿来看下,如何利用查表法来补全骨架代码。
在这里插入图片描述
相对于分支逻辑的实现方式,查表法的代码实现更加清晰,可读性和可维护性更好。当修改状态机时,我们只需要修改transitionTable和
actionTable两个二维数组即可。实际上,如果我们把这两个二维数组存储在配置文件中,当需要修改状态机时,我们甚至可以不修改任何代
码,只需要修改配置文件就可以了。具体的代码如下所示:

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;
    }
}
public class MarioStateMachine {
    private int score;
    private State currentState;
    private static final State[][] transitionTable = {
            {SUPER, CAPE, FIRE, SMALL},
            {SUPER, CAPE, FIRE, SMALL},
            {CAPE, CAPE, CAPE, SMALL},
            {FIRE, FIRE, FIRE, 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;
    }
}

3, 状态模式
在查表法的代码实现中,事件触发的动作只是简单的积分加减,所以,我们用一个int类型的二维数组actionTable就能表示,二维数组中的值
表示积分的加减值。但是,如果要执行的动作并非这么简单,而是一系列复杂的逻辑操作(比如加减积分、写数据库,还有可能发送消息通知
等等),我们就没法用如此简单的二维数组来表示了。这也就是说,查表法的实现方式有一定局限性。

public interface IMario {
    State getName();
    void obtainMushRoom(MarioStateMachine stateMachine);
    void obtainCape(MarioStateMachine stateMachine);
    void obtainFireFlower(MarioStateMachine stateMachine);
    void meetMonster(MarioStateMachine stateMachine);
}

public class SmallMario implements IMario {
    private static final SmallMario instance = new SmallMario();
    private SmallMario() {}
    public static SmallMario getInstance() {
        return instance;
    }

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

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

    @Override
    public void obtainCape(MarioStateMachine stateMachine) {
        stateMachine.setCurrentState(CapeMario.getInstance());
        stateMachine.setScore(stateMachine.getScore() + 200);
    }
    @Override
    public void obtainFireFlower(MarioStateMachine stateMachine) {
        stateMachine.setCurrentState(FireMario.getInstance());
        stateMachine.setScore(stateMachine.getScore() + 300);
    }

    @Override
    public void meetMonster(MarioStateMachine stateMachine) {
// do nothing...
    }
}
// 省略SuperMario、CapeMario、FireMario类...
public class MarioStateMachine {
    private int score;
    private IMario currentState;
    public MarioStateMachine() {
        this.score = 0;
        this.currentState = SmallMario.getInstance();
    }
    public void obtainMushRoom() {
        this.currentState.obtainMushRoom(this);
    }
    public void obtainCape() {
        this.currentState.obtainCape(this);
    }
    public void obtainFireFlower() {
        this.currentState.obtainFireFlower(this);
    }
    public void meetMonster() {
        this.currentState.meetMonster(this);
    }
    public int getScore() {
        return this.score;
    }
    public State getCurrentState() {
        return this.currentState.getName();
    }
    public void setScore(int score) {
        this.score = score;
    }
    public void setCurrentState(IMario currentState) {
        this.currentState = currentState;
    }
}

今天我们总结了三种实现方式。
第一种实现方式叫分支逻辑法。利用if-else或者switch-case分支逻辑,参照状态转移图,将每一个状态转移原模原样地直译成代码。对于简单的状态机来说,这种实现方式最简单、最直接,是首选。

第二种实现方式叫查表法。对于状态很多、状态转移比较复杂的状态机来说,查表法比较合适。通过二维数组来表示状态转移图,能极大地提高代码的可读性和可维护性。

第三种实现方式叫状态模式。对于状态并不多、状态转移也比较简单,但事件触发执行的动作包含的业务逻辑可能比较复杂的状态机来说,我们首选这种实现方式。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值