java设计模式之状态模式

在实际的软件开发中,状态模式不是很常用,但在一些能够用到的场景里,能发挥非常大的作用。

状态模式一般用于实现状态机,而状态机一般用在游戏、工作流引擎等软件开发中。

状态机有三个组成部分:状态、事件和动作。触发某个事件可以改变对象的状态。

以超级马里奥这个小游戏为例,一进入游戏是个小马里奥,吃到蘑菇就会变成超级马里奥,并增加相应积分,碰到怪兽又会变回小马里奥。减去积分。

获得火焰会变成火焰马里奥,获得斗篷会变成斗篷马里奥。

吃蘑菇等就是事件,小马里奥、超级马里奥、火焰马里奥就是状态,增加减少积分就是动作。

当状态转换逻辑比较简单的时候,完全可以在一个类中通过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

 

上一篇:解决Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.7.0:compile


下一篇:Give root password for maintenance (Or press Control-D to continue)