在实际的软件开发中,状态模式不是很常用,但在一些能够用到的场景里,能发挥非常大的作用。
状态模式一般用于实现状态机,而状态机一般用在游戏、工作流引擎等软件开发中。
状态机有三个组成部分:状态、事件和动作。触发某个事件可以改变对象的状态。
以超级马里奥这个小游戏为例,一进入游戏是个小马里奥,吃到蘑菇就会变成超级马里奥,并增加相应积分,碰到怪兽又会变回小马里奥。减去积分。
获得火焰会变成火焰马里奥,获得斗篷会变成斗篷马里奥。
吃蘑菇等就是事件,小马里奥、超级马里奥、火焰马里奥就是状态,增加减少积分就是动作。
当状态转换逻辑比较简单的时候,完全可以在一个类中通过if/else把这些逻辑描述出来。
但当游戏很复杂,状态很多的时候,上面的代码的可读性和可维护性就会变得很差。这个时候就需要引入状态模式。
一、状态和事件
首先,我们通过一个枚举类存储所有状态
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; } }
然后定义一个马里奥接口,用来表示马里奥的所有事件
/** * 状态的接口,定义了所有事件,它的4个子类定义了状态机的所有状态 */ public interface IMario { State getName(); void obtainMushRoom(MarioStateMachine stateMachine);//吃到蘑菇 void obtainCape(MarioStateMachine stateMachine);//获得斗篷 void obtainFireFlower(MarioStateMachine stateMachine);//获得火焰 void meetMonster(MarioStateMachine stateMachine);//遇到怪物 }
二、状态机类
状态机类存储了马里奥的当前积分和状态,是核心逻辑类,但因为我们应用了状态模式。所以具体每个状态的逻辑都被分配到具体的状态类中。
这样状态机类就会变得很轻量小巧。
/** * 原来的状态逻辑都集中在本类,采用if/else分支逻辑比较复杂,现在已经分散到四个状态类中 * 状态机类和四个状态类是双向依赖关系,状态变化的逻辑通过调用状态子类完成实现 */ 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; } }
三、具体状态机类
在状态机中状态是不断切换的,但每个状态本身并不变化,所以我们将每个状态子类设计为单例类。
3.1.小马里奥
/** * 状态机是不断变化的,但状态子类本身是不变的,也没有成员变量,所有设置成单例更为合适 * 设置成单例之后,状态子类就不能在持有状态机变量,但又要对状态机进行修改,可以把状态机作为状态转化的参数 */ 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... } } }
3.2.超级马里奥
public class SuperMario implements IMario { private static final SuperMario instance = new SuperMario(); private SuperMario() { } public static SuperMario getInstance() { return instance; } @Override public State getName() { return State.SUPER; } @Override public void obtainMushRoom(MarioStateMachine stateMachine) { } @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) { stateMachine.setCurrentState(SmallMario.getInstance()); stateMachine.setScore(stateMachine.getScore() - 100); } }
3.3.火焰马里奥
public class FireMario implements IMario { private static final FireMario instance = new FireMario(); private FireMario() { } public static FireMario getInstance() { return instance; } @Override public State getName() { return State.FIRE; } @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.setCurrentState(SmallMario.getInstance()); stateMachine.setScore(stateMachine.getScore() - 300); } }
3.4.斗篷马里奥
public class CapeMario implements IMario { private static final CapeMario instance = new CapeMario(); private CapeMario() { } public static CapeMario getInstance() { return instance; } @Override public State getName() { return State.CAPE; } @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.setCurrentState(SmallMario.getInstance()); stateMachine.setScore(stateMachine.getScore() - 200); } }
四、开始游戏
这样设计,即便是以后再添加新的动作或状态,我们只需要添加新的状态类和方法,而不需要改变原因代码。
真正实现了对扩展开放,都修改关闭。
public class Application { public static void main(String[] args) { //初始化 System.out.println("===========游戏开始==========="); MarioStateMachine mario = new MarioStateMachine(); State currentState = mario.getCurrentState(); System.out.println(mario.getScore()); System.out.println(currentState); //吃蘑菇 System.out.println("===========吃蘑菇==========="); mario.obtainMushRoom(); currentState = mario.getCurrentState(); System.out.println(mario.getScore()); System.out.println(currentState); //获得斗篷 System.out.println("============获得斗篷=========="); mario.obtainCape(); currentState = mario.getCurrentState(); System.out.println(mario.getScore()); System.out.println(currentState); //遇到怪物 System.out.println("===========遇到怪物==========="); mario.meetMonster(); currentState = mario.getCurrentState(); System.out.println(mario.getScore()); System.out.println(currentState); //获得火焰 System.out.println("============获得火焰=========="); mario.obtainFireFlower(); currentState = mario.getCurrentState(); System.out.println(mario.getScore()); System.out.println(currentState); } }
输出:
===========游戏开始=========== SMALL 0 ===========吃蘑菇=========== SUPER 100 ============获得斗篷========== CAPE 300 ===========遇到怪物=========== SMALL 100 ============获得火焰========== FIRE 400