文章

状态模式

状态模式

状态模式

定义

允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。

类图

io0f5

  • 角色说明:
  1. State(抽象状态角色):抽象类或者接口,定义对象的各种状态和行为。
  2. ConcreteState(具体状态角色):实现抽象角色类,定义了本状态下的行为,即要做的事情。
  3. Context(环境角色):定义客户端需要的接口,并且负责具体状态的切换。

案例

  • State(抽象状态角色)
1
2
3
4
public interface PersonState {
    void movies();//看电影
    void shopping();//逛街
}
  • DogState(具体状态角色 1)
1
2
3
4
5
6
7
8
9
10
11
12
13
public class DogState implements PersonState {
    @Override
    public void movies() {
        System.out.println("[单身狗]一个人偷偷看岛国大片");
    }

    @Override
    public void shopping() {
        //单身狗逛条毛街啊
        //空实现
        System.err.println("[单身狗]单身狗逛条毛街啊");
    }
}
  • LoveState(具体状态角色 2)
1
2
3
4
5
6
7
8
9
10
11
public class LoveState implements PersonState {
    @Override
    public void movies() {
        System.out.println("[恋爱中]一起上电影院看大片~");
    }

    @Override
    public void shopping() {
        System.out.println("[恋爱中]一起愉快的逛街去~");
    }
}
  • 创建环境类,负责状态的切换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public final class Context {

    private PersonState mPersonState;

    public void setPersonState(PersonState personState) {
        mPersonState = personState;
    }

    public void movies() {
        mPersonState.movies();
    }

    public void shopping() {
        mPersonState.shopping();
    }
}
  • 测试
1
2
3
4
5
6
7
8
9
10
11
12
public final class TestState {
    public static void main(String[] args) {
        Context context = new Context();
        context.setPersonState(new DogState());
        context.movies();
        context.shopping();

        context.setPersonState(new LoveState());
        context.movies();
        context.shopping();
    }
}

结果:

1
2
3
4
[单身狗]一个人偷偷看岛国大片
[恋爱中]一起上电影院看大片~
[恋爱中]一起愉快的逛街去~
[单身狗]单身狗逛条毛街啊

总结

  1. 处理对象的各种状态互相转换
  2. 相比策略模式,策略模式是选择一种策略,不存在状态切换一说,策略一开始就定了;状态模式可以在不同状态对象之间切换。
  3. 策略模式更符合开闭原则,耦合性更低

状态机

什么是有限状态机?

有限状态机(finite state machine)简称 FSM;状态机就是包含多个状态的数学模型,并可以在状态之间进行变换并且触发一些动作。
一个状态机一般包含以下几个元素:

  1. State 当前状态
  2. Event 触发事件
  3. Transition 状态变换,或者说下一个状态 (次态)
  4. Action 要执行的动作

状态机实现方式

简单场景:

地铁进站闸口的状态有两个:已经关闭、已经开启两个状态。刷卡后闸口从已关闭变为已开启,人通过后闸口状态从已开启变为已关闭。

IndexStateEventNextStateAction
1闸机口 LOCKED投币闸机口 UN_LOCKED闸机口打开闸门
2闸机口 LOCKED通过闸机口 LOCKED闸机口警告
3闸机口 UN_LOCKED投币闸机口 UN _LOCKED闸机口退币
4闸机口 UN_LOCKED通过闸机口 LOCKED闸机口关闭闸门
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// T01
Given:一个Locked的进站闸口
When: 投入硬币
Then:打开闸口

// T02
Given:一个Locked的进站闸口
When: 通过闸口
Then:警告提示

// T03
Given:一个Unocked的进站闸口
When: 通过闸口
Then:闸口关闭

// T04
Given:一个Unlocked的进站闸口
When: 投入硬币
Then:退还硬币

// T05
Given:一个闸机口
When: 非法操作
Then:操作失败

基于 If 或 Switch 语句实现的有限状态机

  1. 保存当前状态
  2. 状态变更时传入变更事件 event, 先通过 if…else 判断是哪个事件,再对当前事件 event 进行 switch…case 确定要执行的操作
  3. 进入下一个状态,并执行 action

if(), switch 语句都是 switch 语句,但是 Switch 是一种 Code Bad Smell,因为它本质上一种重复。当代码中有多处相同的 switch 时,会让系统变得晦涩难懂,脆弱,不易修改。

  • 定义状态:
1
2
3
4
public enum EntranceMachineState {
    LOCKED, // 闸门锁住
    UNLOCKED // 闸门打开
}
  • 定义 Event:
1
2
3
4
public enum Action {
    PASS, // 通过闸门
    INSERT_COIN // 投币
}
  • Transition 状态变换:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class EntranceMachine {

    private EntranceMachineState state;

    public EntranceMachine(EntranceMachineState state) {
        this.state = state;
    }

    public String execute(Action action) {
        if (Objects.isNull(action)) {
            throw new InvalidActionException();
        }

        if (EntranceMachineState.LOCKED.equals(state)) {
            switch (action) {
                case INSERT_COIN:
                    setState(EntranceMachineState.UNLOCKED);
                    return open();
                case PASS:
                    return alarm();
            }
        }

        if (EntranceMachineState.UNLOCKED.equals(state)) {
            switch (action) {
                case PASS:
                    setState(EntranceMachineState.LOCKED);
                    return close();
                case INSERT_COIN:
                    return refund();
            }
        }
        return null;
    }

    // action ↓↓↓
    private String refund() {
        return "refund";
    }

    private String close() {
        return "closed";
    }

    private String alarm() {
        return "alarm";
    }

    private String open() {
        return "opened";
    }
}

基于 State 模式实现的有限状态机 (State Design Pattern)

状态模式主要就是对转换规则进行封装,封装状态而暴露行为,状态的改变看起来就是行为发生改变,总结起来,状态模式干了这么几件事

  1. 需要定义状态抽象类 AbstractState,其中需要包含上下文 Context, 以及所有的抽象事件(event)对象的方法
  2. 具体的状态需要继承并实现抽象状态
  3. 上下文 Context 中需要包含所有所需信息,包括所有的状态实例,并指定一个属性指示当前是哪个状态实例

状态模式将与行为与状态相绑定,避免了直接去写大量的 if…else 和 switch…case 编写时可以方便地增加新的状态,并且只需要改变对象的状态就可以改变对象的行为,

  1. 定义状态接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
 * 状态接口,里面定义了各种action
 */
public interface EntranceMachineState {

    /**
     * 投币
     */
    String insertCoin(EntranceMachine entranceMachine);

    /**
     * 通过闸口
     */
    String pass(EntranceMachine entranceMachine);
}
  1. 状态实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
 * 已经解锁状态
 */
public class LockedEntranceMachineState implements EntranceMachineState {

    @Override
    public String insertCoin(EntranceMachine entranceMachine) {
        return entranceMachine.open();
    }

    @Override
    public String pass(EntranceMachine entranceMachine) {
        return entranceMachine.alarm();
    }
}
/**
 * 未解锁状态
 */
public class UnlockedEntranceMachineState implements EntranceMachineState {

    @Override
    public String insertCoin(EntranceMachine entranceMachine) {
        return entranceMachine.refund();
    }

    @Override
    public String pass(EntranceMachine entranceMachine) {
        return entranceMachine.close();
    }
}
  1. 事件动作枚举类
1
2
3
4
5
6
7
/**
 * 动作
 */
public enum Action {
    PASS, // 通过闸口
    INSERT_COIN // 投币
}
  1. 状态机类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class EntranceMachine {

    // 定义已有的状态
    private EntranceMachineState locked = new LockedEntranceMachineState();
    private EntranceMachineState unlocked = new UnlockedEntranceMachineState();

    // 当前状态
    private EntranceMachineState state;

    public EntranceMachine(EntranceMachineState state) {
        this.state = state;
    }
    
    public String execute(Action action) {
        if (Objects.isNull(action)) {
            throw new InvalidActionException();
        }

        if (Action.PASS.equals(action)) {
            return state.pass(this);
        }

        return state.insertCoin(this);
    }

    public boolean isUnlocked() {
        return state == unlocked;
    }

    public boolean isLocked() {
        return state == locked;
    }

    public String open() {
        setState(unlocked);
        return "opened";
    }

    public String alarm() {
        setState(locked);
        return "alarm";
    }

    public String refund() {
        setState(unlocked);
        return "refund";
    }

    public String close() {
        setState(locked);
        return "closed";
    }

    private void setState(EntranceMachineState state) {
        this.state = state;
    }
}

基于枚举实现的状态机

利用枚举来实现,其主要思想在于可以将状态 State 和事件 Event 都定义成一个枚举类型,并利用枚举也可以定义抽象方法的特性,使得其定义和动作可以写在一起

  1. 定义状态枚举
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public enum StateEnum {
    State1 {
        @Override
        public void doEvent1(StateMachine stateMachine) {
            // set next state
            // do something else
        }

        @Override
        public void doEvent2(StateMachine stateMachine) {
            // set next state
            // do something else
        }
        // ......
    },
    // .......
    ;

    public abstract void doEvent1(StateMachine stateMachine);
    public abstract void doEvent2(StateMachine stateMachine);
    // ......
}
  1. 定义事件枚举
1
2
3
4
5
6
7
8
9
10
11
12
public enum EventEnum {
    Event1 {
        @Override
        public void trigger(StateMachine stateMachine, StateEnum state) {
            return state.doEvent1(stateMachine);
        }
    },
    // ......
    ;

    public abstract void trigger(StateMachine stateMachine, StateEnum state);
}
  1. 组装状态机
1
2
3
4
5
6
7
8
9
10
11
12
public class StateMachine {

    private StateEnum state;

    public StateMachine(StateEnum state) {
        this.state = state;
    }

    public void execute(EventEnum event) {
        event.trigger(this, this.state);
    }
}

基于状态集合实现的有限状态机

将状态机每一个状态转换所需要的几个要素都保存下来,在触发对应的事件时,遍历所有的状态映射记录,并根据状态以及上下文信息找到对应的记录,并执行转换得到次态

  1. 所有的状态机所需数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class EntranceMachineTransaction {

    public EntranceMachineState currentState;

    public Action action;

    public EntranceMachineState nextState;

    public Event event;

    public static EntranceMachineTransaction instance() {
        return new EntranceMachineTransaction();
    }
}
  1. 状态机转换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class EntranceMachine {

    static List<EntranceMachineTransaction> entranceMachineTransactionList = new ArrayList<>();
    static {
        EntranceMachineTransaction instance1 = EntranceMachineTransaction.instance();
        instance1.currentState = EntranceMachineState.LOCKED;
        instance1.action = Action.INSERT_COIN;
        instance1.nextState = EntranceMachineState.LOCKED;
        instance1.event = new OpenEvent();
        entranceMachineTransactionList.add(instance1);


        EntranceMachineTransaction instance2 = EntranceMachineTransaction.instance();
        instance2.currentState = EntranceMachineState.UNLOCKED;
        instance2.action = Action.PASS;
        instance2.nextState = EntranceMachineState.LOCKED;
        instance2.event = new AlarmEvent();
        entranceMachineTransactionList.add(instance2);

        EntranceMachineTransaction instance3 = EntranceMachineTransaction.instance();
        instance3.currentState = EntranceMachineState.LOCKED;
        instance3.action = Action.PASS;
        instance3.nextState = EntranceMachineState.UNLOCKED;
        instance3.event = new CloseEvent();
        entranceMachineTransactionList.add(instance3);

        EntranceMachineTransaction instance4 = EntranceMachineTransaction.instance();
        instance4.currentState = EntranceMachineState.UNLOCKED;
        instance4.action = Action.INSERT_COIN;
        instance4.nextState = EntranceMachineState.UNLOCKED;
        instance4.event = new RefundEvent();
        entranceMachineTransactionList.add(instance4);
    }


    private EntranceMachineState state;

    public EntranceMachine(EntranceMachineState state) {
        setState(state);
    }

    private void setState(EntranceMachineState state) {
        this.state = state;
    }

    public String execute(Action action) {
        Optional<EntranceMachineTransaction> transactionOptional = entranceMachineTransactionList
                .stream()
                .filter(transaction ->
                        transaction.action.equals(action) && transaction.currentState.equals(state))
                .findFirst();

        if (!transactionOptional.isPresent()) {
            throw new InvalidActionException();
        }

        EntranceMachineTransaction transaction = transactionOptional.get();
        setState(transaction.nextState);
        return transaction.event.execute();
    }
}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test {

    public static void main(String[] args) {
        EntranceMachineState state = EntranceMachineState.LOCKED;
        EntranceMachine stateMachine = new EntranceMachine(state);

        String execute = stateMachine.execute(Action.INSERT_COIN);
        System.out.println(execute + ",curState=" + stateMachine.getState());
        String execute1 = stateMachine.execute(Action.PASS);
        System.out.println(execute1 + ",curState=" + stateMachine.getState());
        String execute2 = stateMachine.execute(Action.PASS);
        System.out.println(execute2 + ",curState=" + stateMachine.getState());
    }
}

输出:

1
2
3
opened,curState=UNLOCKED
alarm,curState=LOCKED
closed,curState=UNLOCKED

案例

电梯

我们每天都乘坐电梯,电梯有四种状态:开门、关门、运行、停止。d2s9e

  1. 定义电梯状态接口,里面定义电梯的行为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
 * 电梯状态接口
 *
 * 定义电梯行为:打开、关闭、运行、停止
 */
public abstract class LiftState {

    // 拥有一个电梯对象,用于更新电梯当前状态
    protected Lift mLift;

    /**
     * 通过构造函数引入电梯的实例化对象
     */
    public LiftState(Lift lift) {
        this.mLift = lift;
    }

    /**
     * 行为:打开电梯门
     */
    public abstract void open();

    /**
     * 行为:关闭电梯门
     */
    public abstract void close();

    /**
     * 行为:电梯运行
     */
    public abstract void run();

    /**
     * 行为:电梯停止运行
     */
    public abstract void stop();
}
  1. 电梯状态实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// 电梯处于关闭状态
public class ClosingState extends LiftState {
    public ClosingState(Lift lift) {
        super(lift);
    }

    @Override
    public void open() {
        // 执行开门动作
        // 1、变化为开门状态
        this.mLift.setState(mLift.getOpenningState());
        // 2、开门
        this.mLift.open();
    }

    @Override
    public void close() {
        System.out.println("执行关门动作");
    }

    @Override
    public void run() {
        // 运行动作
        // 1、运行状态
        this.mLift.setState(mLift.getRunningState());
        // 2、运行动作
        this.mLift.run();
    }

    @Override
    public void stop() {
        // 停止动作
        // 1、转化为停止状态
        this.mLift.setState(mLift.getStoppingState());
        // 2、停止
        this.mLift.stop();
    }
}
// 打开状态
public class OpeningState extends LiftState {

    public OpeningState(Lift lift) {
        super(lift);
    }

    @Override
    public void open() {
        // 执行开门动作
        System.out.println("执行开门动作");
    }

    @Override
    public void close() {
        // 执行关门动作
        // 1、转化为关门状态
        mLift.setState(mLift.getCloseingState());
        // 2、关门
        mLift.close();
    }

    @Override
    public void run() {
        // do noting
        // 开门状态,不能执行运行动作

    }

    @Override
    public void stop() {
        // do noting
        // 开门状态下,不执行停止动作
    }
}
// 运行状态
public class RunningState extends LiftState {

    public RunningState(Lift lift) {
        super(lift);
    }

    @Override
    public void open() {
        // do noting
    }

    @Override
    public void close() {
        // do noting
    }

    @Override
    public void run() {
        // 运行动作
        System.out.println("电梯上下运行中...");
    }

    @Override
    public void stop() {
        // 停止动作
        // 1、转化为停止状态
        this.mLift.setState(mLift.getStoppingState());
        // 2、停止动作
        this.mLift.stop();
    }
}
// 停止状态
public class StoppingState extends LiftState {

    public StoppingState(Lift lift) {
        super(lift);
    }

    @Override
    public void open() {
        // 开门动作
        // 1、开门状态
        this.mLift.setState(mLift.getOpenningState());
        // 2、执行开门动作
        this.mLift.open();
    }

    @Override
    public void close() {
        // do noting
    }

    @Override
    public void run() {
        // 运行动作
        // 1、运行状态
        this.mLift.setState(mLift.getRunningState());
        // 2、运行动作
        this.mLift.run();
    }

    @Override
    public void stop() {
        // 电梯停止动作
        System.out.println("电梯停止运行...");
    }
}
  1. 电梯状态机类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/**
 * 定义电梯类
 */
public class Lift {
    //定义出电梯的所有状态
    private LiftState openningState;
    private LiftState closingState;
    private LiftState runningState;
    private LiftState stoppingState;

    // 定义当前电梯状态
    private LiftState mCurState;


    /**
     * 构造方法
     */
    public Lift() {
        openningState = new OpeningState(this);
        closingState = new ClosingState(this);
        runningState = new RunningState(this);
        stoppingState = new StoppingState(this);
    }

    /**
     * 执行开门动作
     */
    public void open() {
        mCurState.open();
    }

    /**
     * 执行关门动作
     */
    public void close() {
        mCurState.close();
    }

    /**
     * 执行运行动作
     */
    public void run() {
        mCurState.run();
    }

    /**
     * 执行停止动作
     */
    public void stop() {
        mCurState.stop();
    }

    // ##################设置当前电梯状态#####################

    /**
     * 设置当前电梯状态
     */
    public void setState(LiftState state) {
        this.mCurState = state;
    }

    public LiftState getCurState() {
        return mCurState;
    }

    // ###################获取电梯的全部状态####################

    public LiftState getOpenningState() {
        return openningState;
    }

    public LiftState getCloseingState() {
        return closingState;
    }

    public LiftState getRunningState() {
        return runningState;
    }

    public LiftState getStoppingState() {
        return stoppingState;
    }
}
  1. 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test {
    public static void main(String[] args) {
        Lift lift = new Lift();
        lift.setState(new ClosingState(lift));
        System.out.println("当前state=" + lift.getCurState().getClass().getName());
        lift.open();
        System.out.println("执行open,当前state=" + lift.getCurState().getClass().getName());
        lift.close();
        System.out.println("执行close,当前state=" + lift.getCurState().getClass().getName());
        lift.run();
        System.out.println("执行run,当前state=" + lift.getCurState().getClass().getName());
        lift.stop();
        System.out.println("执行stop,当前state=" + lift.getCurState().getClass().getName());
    }
}

输出:

1
2
3
4
5
6
7
8
9
当前state=com.hacket.designpattern.状态机.案例.电梯.state.ClosingState
执行开门动作
执行open,当前state=com.hacket.designpattern.状态机.案例.电梯.state.OpeningState
执行关门动作
执行close,当前state=com.hacket.designpattern.状态机.案例.电梯.state.ClosingState
电梯上下运行中...
执行run,当前state=com.hacket.designpattern.状态机.案例.电梯.state.RunningState
电梯停止运行...
执行stop,当前state=com.hacket.designpattern.状态机.案例.电梯.state.StoppingState

remix 瞄仔游戏状态机实现状态转移

瞄仔游戏状态介绍

1
2
3
4
5
游戏分为4个阶段:1下注阶段、2开奖阶段、3等待开始阶段、4游戏结束
1. 下注阶段: 文案提示下注中,喵仔下注倒计时时间:40*1000ms
2. 等待开始阶段:后端开始计算结果,这个阶段轮询后端计算结果,文案提示准备出发 ,喵仔出发前等待时间:3*1000ms
3. 开始阶段:后端结果计算完成,瞄仔开始跑动画,喵仔动画时长:5*1000ms
4. 游戏结束阶段:弹出中奖结果弹窗

不用状态机写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
sealed class Stage(val value: Int, val desc: String = "") {
    var gameId: String? = ""
    companion object {
        fun of(value: Int?): Stage {
            return when (value) {
                GameBetingStage.value -> GameBetingStage
                GameWaitStartStage.value -> GameWaitStartStage
                GameStartStage.value -> GameStartStage
                GameOver.value -> GameOver

                else -> GameUnknown
            }
        }
        fun of(game: KittyGameItem?): Stage {
            val stage = when (game?.stage) {
                GameBetingStage.value -> GameBetingStage
                GameWaitStartStage.value -> GameWaitStartStage
                GameStartStage.value -> GameStartStage
                GameOver.value -> GameOver

                else -> GameUnknown
            }
            stage.gameId = game?.gameId
            return stage
        }
    }

    object GameBetingStage : Stage(1, "下注阶段(1)")
    object GameWaitStartStage : Stage(2, "等待开始阶段(2)")
    object GameStartStage : Stage(3, "开始阶段(3)")
    object GameOver : Stage(4, "游戏结束阶段(4)")
    object GameUnknown : Stage(5, "未知阶段(5)")
    // 下一个阶段
    fun moveToNextStage(): Stage {
        return when (this) {
            GameBetingStage -> GameWaitStartStage
            GameWaitStartStage -> GameStartStage
            GameStartStage -> GameOver
            GameOver -> GameUnknown
            GameUnknown -> GameBetingStage
        }
    }
}

private suspend fun KittyGameInfo.handleStage() {
    return when (Stage.of(game.stage)) {
        Stage.GameBetingStage -> { // 下注阶段,更新UI
            val totalTimeSeconds = game.betLeftTime() / 1000L
            mGameTitleJob?.cancel()
            mGameTitleJob = countDownCoroutines(totalTimeSeconds.toInt(), viewModelScope, {}) {
                _gameTitleLiveData.value = game.getGoHomeBetStageTitle()
            }
            delay(game.currentStageLeftTimeMillis(game.stage))
            mGameTitleJob?.cancel()
            moveToNextStage()
        }
        Stage.GameWaitStartStage -> { // 等待开始阶段
            _gameTitleLiveData.value = getGoHomeTitle()
            animateToCrossing()

            viewModelScope.launch {
                delay(goCrossDuration)
                val data = withTimeoutOrNull(game.computeRewardLeftTime() - 100) {
                    retryGetGameResultSuspend(game.gameId)
                }
                gameReward = data
                if (gameReward == null) {
                    _gameStatusLiveData.value = false
                    ToastUtil.Short("服务器异常,请稍后再重试")
                    gameOver()
                }
            }
            delay(game.currentStageLeftTimeMillis(game.stage))
            moveToNextStage()
        }
        Stage.GameStartStage -> {
            animationDuration = game.animDuration()
            _gameTitleLiveData.value = getGoHomeTitle()
            startAnim()
            delay(game.currentStageLeftTimeMillis(game.stage))
            moveToNextStage()
        }
        Stage.GameOver -> {
            gameOverStage(from = "正常的游戏流程handleStage")
        }
        else -> {
            LogUtils.e(TAG, "[handleStage]${Stage.of(game)} ")
        }
    }
}

用状态机写法

  • 状态抽象类
1
2
3
abstract class GameState(val scope: CoroutineScope, val game: Game) {
    abstract suspend fun doAction()
}
  • 各种状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
 * 下注阶段(1)
 */
class GameBetingState(scope: CoroutineScope, game: Game) : GameState(scope, game) {
    override suspend fun doAction() {
        println("下注阶段(1),倒计时更新文案... 等待30秒")
        delay(30_000L)
        game.setState(game.mGameWaitStartState)
    }
}
/**
 * 等待开始阶段(2)
 */
class GameWaitStartState(scope: CoroutineScope, game: Game) : GameState(scope, game) {
    override suspend fun doAction() {
        println("等待开始阶段(2),后端开始计算结果,这个阶段轮询后端计算结果,3秒")
        delay(3000L)
        game.setState(game.mGameStartState)
    }
}
/**
 * 开始阶段(3)
 */
class GameStartState(scope: CoroutineScope, game: Game) : GameState(scope, game) {
    override suspend fun doAction() {
        println("开始阶段(3),后端结果计算完成,瞄仔开始跑动画,5秒")
        delay(5_000L)
        game.setState(game.mGameOverState)
    }
}
/**
 * 游戏结束阶段(4)
 */
class GameOverState(scope: CoroutineScope, game: Game) : GameState(scope, game) {
    override suspend fun doAction() {
        println("游戏结束阶段(4),弹出中奖结果弹窗,开启下一局")
        game.setState(game.mGameBetingState)
    }
}
  • 状态机
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class GameMachine(val scope: CoroutineScope) {

    val mGameBetingState = GameBetingState(scope, this)
    val mGameWaitStartState = GameWaitStartState(scope, this)
    val mGameStartState = GameStartState(scope, this)
    val mGameOverState = GameOverState(scope, this)

    var curState: GameState? = null

    suspend fun nextStage() {
        curState?.doAction()
    }

    fun setState(state: GameState) {
        curState = state
    }
}
  • 测试
1
2
3
4
5
6
7
8
9
fun main() = runBlocking {
    val game = GameMachine(this)
    game.setState(GameBetingState(this, game))
    println("开始游戏,当前状态${game.curState?.javaClass?.simpleName}")
    while (isActive) {
        game.nextStage()
        println("下一步,当前状态${game.curState?.javaClass?.simpleName}")
    }
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
开始游戏,当前状态GameBetingState
下注阶段(1),倒计时更新文案... 等待30秒
下一步,当前状态GameWaitStartState
等待开始阶段(2),后端开始计算结果,这个阶段轮询后端计算结果,3秒
下一步,当前状态GameStartState
开始阶段(3),后端结果计算完成,瞄仔开始跑动画,5秒
下一步,当前状态GameOverState
游戏结束阶段(4),弹出中奖结果弹窗,开启下一局
下一步,当前状态GameBetingState
下注阶段(1),倒计时更新文案... 等待30秒
下一步,当前状态GameWaitStartState
等待开始阶段(2),后端开始计算结果,这个阶段轮询后端计算结果,3秒
下一步,当前状态GameStartState
开始阶段(3),后端结果计算完成,瞄仔开始跑动画,5秒
下一步,当前状态GameOverState
游戏结束阶段(4),弹出中奖结果弹窗,开启下一局
下一步,当前状态GameBetingState
下注阶段(1),倒计时更新文案... 等待30秒
下一步,当前状态GameWaitStartState
等待开始阶段(2),后端开始计算结果,这个阶段轮询后端计算结果,3秒
下一步,当前状态GameStartState
开始阶段(3),后端结果计算完成,瞄仔开始跑动画,5秒
下一步,当前状态GameOverState

Ref

本文由作者按照 CC BY 4.0 进行授权