设计模式--行为型--状态模式

状态模式在实际的软件开发中并不常用,但在能够用到它的场景能够发挥重要作用。状态模式一般用来实现状态机,而状态机常用在游戏、工作流引擎等系统的开发中。不过状态台机的实现有多种,比较常用的是分支逻辑法和查表法

 

状态模式,英文翻译State Pattern。允许对象内部状态发生改变时,对象看起来好像修改了它的类。

 

主要解决:对象行为依赖于它的状态(属性),并且可以根据它的状态来改变它的相关行为。一般用来实现状态机

 

什么是有限状态机?

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

 

上面给出了状态机的定义,那结合“超级马里奥”这款游戏来进行说明:

在游戏中,马里奥可有变身为多种形态,比如:小马里奥(Small Mario)、超级马里奥(Super Mario)、斗篷马里奥(Cape Mario)、火焰马里奥(fire Mario)等等,在不同的游戏情节下,各种形态相互转换,并且相应的增减积分。比如:初始形态是小马里奥,吃了蘑菇之后就会变成超级马里奥,并且增加100积分。

 

实际上,马里奥形态的转变就是一个状态机。其中,马里奥的不同形态就是状态机中的不同"状态",游戏情节(比如吃蘑菇)就是状态机中的“事件”,加减积分就是状态机中的“动作”。比如,吃蘑菇这个"事件",会触发状态转移:从小马里奥变身为超级马里奥,以及触发“动作”的执行(增加100积分)。

 

下面对游戏进行了简化,只保留了部分事件和状态,简化后的状态转移如下图所示:

如何实现上面的状态机呢?首先会先给出骨架代码,然后给出使用if-else分支逻辑法的实现方式,最后会使用状态模式的方式进行实现。

 

马里奥状态机的骨架代码

如下所示:状态机中包含,吃了蘑菇(obtainMushroom())、获得斗篷(obtainCape)、获得火焰(obtainFireFlower())、遇到怪物(meetMonster())这几个事件对应的方法。这些方法会根据当前状态和事件,更新状态和增减积分。

骨架代码代码如下:

/**
 * 马里奥的状态枚举类
 */
public enum MarioState {
    SMALL(0,"小马里奥"),
    SUPPER(1,"超级马里奥"),
    CAPE(2,"斗篷马里奥"),
    FIRE(3,"火焰马里奥"),
    ;

    private int state;
    private String remark;

    MarioState(int state, String remark) {
        this.state = state;
        this.remark = remark;
    }

    public int getState() {
        return state;
    }

    public String getRemark() {
        return remark;
    }
}

/**
 * 马里奥状态机
 */
public class MairoStateMachine {
    private int score;
    private MarioState currentState;

    public MairoStateMachine() {
        this.score=0;
        currentState=MarioState.SMALL;
    }

    //吃了蘑菇
    public void obtainMushroom(){

    }

    //获得斗篷
    public void obtainCape(){
        
    }

    //获得火焰
    public void obtainFireFlower(){
       
    }

    //碰到小怪物
    public void meetMonster(){
        
    }

    public int getScore() {
        return score;
    }

    public MarioState getCurrentState() {
        return currentState;
    }
}


/**
 * 测试
 */
public class Demo {
    @Test
    public void test() {
        MairoStateMachine mario=new MairoStateMachine();
        mario.obtainMushroom();
        int score=mario.getScore();
        MarioState state=mario.getCurrentState();
        System.out.println("mario score:"+ score +";  state:"+state.getRemark());
    }
}

 

if-else分支逻辑法实现

上面状态图中,“动作”这一部分比较简单。直接参照上面的状态图,将每一个状态转移和动作的执行直接翻译为代码就可以了。这样编写的代码会存在缺陷,存在大量的if-else。

分支逻辑法实现代码如下:

/**
 * 马里奥状态机
 */
public class MairoStateMachine {
    private int score;
    private MarioState currentState;

    public MairoStateMachine() {
        this.score=0;
        currentState=MarioState.SMALL;
    }

    //吃了蘑菇
    public void obtainMushroom(){
        if(currentState.equals(MarioState.SMALL)){
            this.currentState = MarioState.SUPPER; //状态更新
            score+=100;  //执行"动作"
        }
    }

    //获得斗篷
    public void obtainCape(){
        if(currentState.equals(MarioState.SMALL) || currentState.equals(MarioState.SUPPER)){
            this.currentState = MarioState.CAPE;
            score+=200;
        }
    }

    //获得火焰
    public void obtainFireFlower(){
        if(currentState.equals(MarioState.SMALL) || currentState.equals(MarioState.SUPPER)){
            this.currentState = MarioState.FIRE;
            score+=300;
        }
    }

    //碰到小怪物
    public void meetMonster(){
        if(currentState.equals(MarioState.SUPPER)){
            this.currentState = MarioState.SMALL;
            score-=100;
        }else if(currentState.equals(MarioState.CAPE)){
            this.currentState = MarioState.SMALL;
            score-=200;
        }else if(currentState.equals(MarioState.FIRE)){
            this.currentState = MarioState.SMALL;
            score-=300;
        }
    }

    public int getScore() {
        return score;
    }

    public MarioState getCurrentState() {
        return currentState;
    }
}

如果状态机中的“动作”简单,事件也不多的话,这样实现是完全没问题的。

 

状态模式实现

对于设计模式学习,画出类图,可以加深对模式的理解,类图如下:

其中IMario是状态的接口,定义了所有事件。SmallMario、SupperMario、CapeMario、FireMario是IMario接口的实现类。分别对应着状态机中的4个状态。代码实现如下。

/**
 * 马里奥的状态接口类
 */
public interface IMario {
    MarioState getName();
    void obtainMushroom();
    void obtainCape();
    void obtainFireFlower();
    void meetMonster();
}

/**
 * 马里奥的状态抽线类
 *
 * 用于具体的状态类只重写自己所拥有的事件,屏蔽其没有拥有的事件
 */
public abstract class AbstraceMario implements IMario {
    protected MarioStateMachine marioStateMachine;

    public AbstraceMario(MarioStateMachine marioStateMachine) {
        this.marioStateMachine = marioStateMachine;
    }

    @Override
    public MarioState getName() {return null;}

    @Override
    public void obtainMushroom() {}

    @Override
    public void obtainCape() {}

    @Override
    public void obtainFireFlower() {}

    @Override
    public void meetMonster() {}
}

/**
 * 小马里奥
 *
 * 用于处理小马里奥所用于的全部事件
 */
public class SmallMario extends AbstraceMario {

    public SmallMario(MarioStateMachine marioStateMachine) {
        super(marioStateMachine);
    }

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

    @Override
    public void obtainMushroom() {
        marioStateMachine.setCurrentState(new SupperMario(marioStateMachine));
        marioStateMachine.setScore(marioStateMachine.getScore()+100);
    }

    @Override
    public void obtainCape() {
        marioStateMachine.setCurrentState(new CapeMario(marioStateMachine));
        marioStateMachine.setScore(marioStateMachine.getScore()+200);
    }

    @Override
    public void obtainFireFlower() {
        marioStateMachine.setCurrentState(new FireMairo(marioStateMachine));
        marioStateMachine.setScore(marioStateMachine.getScore()+3300);
    }
}

/**
 * 超级马里奥
 */
public class SupperMario extends AbstraceMario {
    public SupperMario(MarioStateMachine marioStateMachine) {
        super(marioStateMachine);
    }

    @Override
    public MarioState getName() {
        return MarioState.SUPPER;
    }

    @Override
    public void obtainCape() {
        marioStateMachine.setCurrentState(new CapeMario(marioStateMachine));
        marioStateMachine.setScore(marioStateMachine.getScore()+200);
    }

    @Override
    public void obtainFireFlower() {
        marioStateMachine.setCurrentState(new FireMairo(marioStateMachine));
        marioStateMachine.setScore(marioStateMachine.getScore()+300);
    }

    @Override
    public void meetMonster() {
        marioStateMachine.setCurrentState(new SupperMario(marioStateMachine));
        marioStateMachine.setScore(marioStateMachine.getScore()-100);
    }
}

/**
 * 斗篷马里奥
 */
public class CapeMario extends AbstraceMario {
    public CapeMario(MarioStateMachine marioStateMachine) {
        super(marioStateMachine);
    }

    @Override
    public MarioState getName() {
        return MarioState.CAPE;
    }

    @Override
    public void meetMonster() {
        marioStateMachine.setCurrentState(new SupperMario(marioStateMachine));
        marioStateMachine.setScore(marioStateMachine.getScore()-100);
    }
}

/**
 * 火焰马里奥
 */
public class FireMairo extends AbstraceMario {
    public FireMairo(MarioStateMachine marioStateMachine) {
        super(marioStateMachine);
    }

    @Override
    public MarioState getName() {
        return MarioState.FIRE;
    }

    @Override
    public void meetMonster() {
        marioStateMachine.setCurrentState(new SupperMario(marioStateMachine));
        marioStateMachine.setScore(marioStateMachine.getScore()-100);
    }
}

 

/**
 * 马里奥状态机
 */
public class MarioStateMachine {
    private int score;
    private IMario currentState;  //不在使用枚举来表示

    public MarioStateMachine() {
        this.score = 0;
        this.currentState = new SmallMario(this);
    }

    //吃了蘑菇
    public void obtainMushroom(){
        this.currentState.obtainMushroom();
    }

    //获得斗篷
    public void obtainCape(){
        this.currentState.obtainCape();
    }

    //获得火焰
    public void obtainFireFlower(){
        this.currentState.obtainFireFlower();
    }

    //碰到小怪物
    public void meetMonster(){
        this.currentState.meetMonster();
    }
    public int getScore() {
        return score;
    }

    public IMario getCurrentState() {
        return currentState;
    }

    public void setScore(int score) {
        this.score = score;
    }

    public void setCurrentState(IMario currentState) {
        this.currentState = currentState;
    }
}

测试代码:

    @Test
    public void test(){
        MarioStateMachine stateMachine = new MarioStateMachine();
        stateMachine.obtainMushroom();
        stateMachine.obtainFireFlower();
        int score=stateMachine.getScore();
        IMario state=stateMachine.getCurrentState();
        System.out.println("mario score:"+ score +";  state:"+state.getName().getRemark());
    }

执行结果:

从上面代码可以看出,原来所有的状态转移和动作代码执行逻辑,都集中在MarioStateMachine 。现在这些代码逻辑被分散到了各个状态类中。。

这里有一点需要关注,MarioStateMachine和各个状态类之间是双向依赖关系。MarioStateMachine依赖状态类是理所当然的,而反过来,状态类为什么要依赖MarioStateMachine呢?因为各个状态类需要更新MarioStateMachine中的score 和 currentState。

 

总结:

状态模式:需要定义一个状态接口,包含了状态机中的所有事件,有多少个状态,就有多少个状态类。状态类中的事件需要完成两件事,一件是业务逻辑的实现,一件是状态的转移。

如果业务逻辑简单,比较推荐使用if-else分支。

如果业务逻辑比较复杂,那就推荐使用状态机来实现。

 

参考:设计模式之美--王争

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值