什么是有限状态机?
有限状态机,英文翻译是 Finite State Machine,缩写为 FSM,简称为状态机。状态机有 3 个组成部分:状态(State)、事件(Event)、动作(Action)。 其中,事件也称为转移条件(Transition Condition)。事件触发状态的转移及动作的执行。不过,动作不是必须的,也可能只转移状态,不执行任何动作。
对于刚刚给出的状态机的定义,结合一个具体的例子,来进一步解释一下。
“超级马里奥”游戏不知道你玩过没有?在游戏中,马里奥可以变身为多种形态,比如小马里奥(Small Mario)、超级马里奥(Super Mario)、火焰马里奥(Fire Mario)、斗篷马里奥(Cape Mario)等等。在不同的游戏情节下,各个形态会互相转化,并相应的增减积分。比如,初始形态是小马里奥,吃了蘑菇之后就会变成超级马里奥,并且增加 100 积分。
实际上,马里奥形态的转变就是一个状态机。其中,马里奥的不同形态就是状态机中的“状态”,游戏情节(比如吃了蘑菇)就是状态机中的“事件”,加减积分就是状态机中的“动作”。比如,吃蘑菇这个事件,会触发状态的转移:从小马里奥转移到超级马里奥,以及触发动作的执行(增加 100 积分)。
为了方便接下来的讲解,对游戏背景做了简化,只保留了部分状态和事件。简化之后的状态转移如下图所示:
- E1: 吃了蘑菇
- E2: 获取斗篷
- E3: 获取火焰
- E4: 遇到怪物
我们如何编程来实现上面的状态机呢?换句话说,如何将上面的状态转移图翻译成代码呢?
代码骨架如下
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;
}
}
public class MarioStateMachine {
private int score;
private State currentState;
public MarioStateMachine() {
this.score = 0;
this.currentState = State.SMALL;
}
public void obtainMushRoom() {
//TODO
}
public void obtainCape() {
//TODO
}
public void obtainFireFlower() {
//TODO
}
public void meetMonster() {
//TODO
}
public int getScore() {
return this.score;
}
public State getCurrentState() {
return this.currentState;
}
}
public class ApplicationDemo {
public static void main(String[] args) {
MarioStateMachine mario = new MarioStateMachine();
mario.obtainMushRoom();
int score = mario.getScore();
State state = mario.getCurrentState();
System.out.println("mario score: " + score + "; state: " + state);
}
}
状态机实现方式一:代码直译
最简单直接的实现方式是,参照状态转移图,将每一个状态转移,原模原样地直译成代码。这样编写的代码会包含大量的 if-else 或 switch-case 分支判断逻辑,甚至是嵌套的分支判断逻辑。
按照这个实现思路,将上面的骨架代码补全一下。补全之后的代码如下所示:
public 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;
}
}
对于简单的状态机来说,这种实现方式是可以接受的。但是,对于复杂的状态机来说,这种实现方式极易漏写或者错写某个状态转移。除此之外,代码中充斥着大量的 if-else 或者 switch-case 分支判断逻辑,可读性和可维护性都很差。如果哪天修改了状态机中的某个状态转移,我们要在冗长的分支逻辑中找到对应的代码进行修改,很容易改错,引入 bug。
状态机实现方式二:查表法
实际上,上面这种实现方法有点类似 hard code,对于复杂的状态机来说不适用,而状态机的第二种实现方式查表法,就更加合适了。
实际上,除了用状态转移图来表示之外,状态机还可以用二维表来表示,如下所示。在这个二维表中,第一维表示当前状态,第二维表示事件,值表示当前状态经过事件之后,转移到的新状态及其执行的动作。
| 标题 | E1(Got MushRoom) | E2(Got Cape) | E3(Got Fire Flower) | E4(Met Monster) |
|---|---|---|---|---|
| small | Super/+100 | cape/+200 | Fire/+300 | / |
| super | / | cape/+200 | Fire/+300 | small/-100 |
| cape | / | / | / | small/-200 |
| fire | / | / | / | small/-300 |
表中的斜杠表示不存在这种转移
相对于第一种实现方式,查表法的代码实现更加清晰,可读性和可维护性更好。当修改状态机时,我们只需要修改 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;
}
}
状态机实现方式三:状态模式
在查表法的代码实现中,事件触发的动作只是简单的积分加减,所以,我们用一个 int 类型的二维数组 actionTable 就能表示,二维数组中的值表示积分的加减值。但是,如果要执行的动作并非这么简单,而是一系列复杂的逻辑操作(比如加减积分、写数据库,还有可能发送消息通知等等),我们就没法用如此简单的二维数组来表示了。这也就是说,查表法的实现方式有一定局限性。
状态模式通过将事件触发的状态转移和动作执行,拆分到不同的状态类中,来避免分支判断逻辑。我们还是结合代码来理解这句话。
利用状态模式,我们来补全 MarioStateMachine 类,补全后的代码如下所示。
其中,IMario 是状态的接口,定义了所有的事件。SmallMario、SuperMario、CapeMario、FireMario 是 IMario 接口的实现类,分别对应状态机中的 4 个状态。原来所有的状态转移和动作执行的代码逻辑,都集中在 MarioStateMachine 类中,现在,这些代码逻辑被分散到了这 4 个状态类中。
public class FiniteStateMachineDemo {
public static void main(String[] args) {
FiniteStateMachineDemo.MarioStateMachine machine = new FiniteStateMachineDemo().new MarioStateMachine();
machine.obtainMushRoom();
System.out.println(machine.getCurrentState()); //SUPER
machine.obtainFireFlower();
System.out.println(machine.getCurrentState());//FIRE
machine.obtainCape();
System.out.println(machine.getCurrentState()); //FIRE
}
enum State {
SMALL, SUPER, CAPE, FIRE
}
interface IMario { //所有状态类的接口
State getName();
//以下是定义的事件
// 获得蘑菇
void obtainMushRoom();
// 获得斗篷
void obtainCape();
// 获得火焰
void obtainFireFlower();
// 遇到怪物
void meetMonster();
}
class SmallMario implements IMario {
private MarioStateMachine stateMachine;
public SmallMario(MarioStateMachine stateMachine) {
this.stateMachine = stateMachine;
}
@Override
public State getName() {
return State.SMALL;
}
@Override
public void obtainMushRoom() {
stateMachine.setCurrentState(new SuperMario(stateMachine));
stateMachine.setScore(stateMachine.getScore() + 100);
}
@Override
public void obtainCape() {
stateMachine.setCurrentState(new CapeMario(stateMachine));
stateMachine.setScore(stateMachine.getScore() + 200);
}
@Override
public void obtainFireFlower() {
stateMachine.setCurrentState(new FireMario(stateMachine));
stateMachine.setScore(stateMachine.getScore() + 300);
}
@Override
public void meetMonster() {
// do nothing...
}
}
class SuperMario implements IMario {
private MarioStateMachine stateMachine;
public SuperMario(MarioStateMachine stateMachine) {
this.stateMachine = stateMachine;
}
@Override
public State getName() {
return State.SUPER;
}
@Override
public void obtainMushRoom() {
// do nothing...
}
@Override
public void obtainCape() {
stateMachine.setCurrentState(new CapeMario(stateMachine));
stateMachine.setScore(stateMachine.getScore() + 200);
}
@Override
public void obtainFireFlower() {
stateMachine.setCurrentState(new FireMario(stateMachine));
stateMachine.setScore(stateMachine.getScore() + 300);
}
@Override
public void meetMonster() {
stateMachine.setCurrentState(new SmallMario(stateMachine));
stateMachine.setScore(stateMachine.getScore() - 100);
}
}
class CapeMario implements IMario {
private MarioStateMachine stateMachine;
public CapeMario(MarioStateMachine stateMachine) {
this.stateMachine = stateMachine;
}
@Override
public State getName() {
return State.CAPE;
}
@Override
public void obtainMushRoom() {
// do nothing...
}
@Override
public void obtainCape() {
// do nothing...
}
@Override
public void obtainFireFlower() {
stateMachine.setCurrentState(new FireMario(stateMachine));
stateMachine.setScore(stateMachine.getScore() + 300);
}
@Override
public void meetMonster() {
stateMachine.setCurrentState(new SmallMario(stateMachine));
stateMachine.setScore(stateMachine.getScore() - 200);
}
}
class FireMario implements IMario {
private MarioStateMachine stateMachine;
public FireMario(MarioStateMachine stateMachine) {
this.stateMachine = stateMachine;
}
@Override
public State getName() {
return State.FIRE;
}
@Override
public void obtainMushRoom() {
// do nothing...
}
@Override
public void obtainCape() {
// do nothing...
}
@Override
public void obtainFireFlower() {
// do nothing...
}
@Override
public void meetMonster() {
stateMachine.setCurrentState(new SmallMario(stateMachine));
stateMachine.setScore(stateMachine.getScore() - 300);
}
}
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 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;
}
}
}
MarioStateMachine 依赖各个状态类是理所当然的,但是,反过来,各个状态类为什么要依赖 MarioStateMachine 呢?这是因为,各个状态类需要更新 MarioStateMachine 中的两个变量,score 和 currentState。
将状态类设计成单例
实际上,上面的代码还可以继续优化,我们可以将状态类设计成单例,毕竟状态类中不包含任何成员变量。但是,当将状态类设计成单例之后,我们就无法通过构造函数来传递 MarioStateMachine 了,而状态类又要依赖 MarioStateMachine,那该如何解决这个问题呢?
我们可以通过函数参数将 MarioStateMachine 传递进状态类。根据这个设计思路,我们对上面的代码进行重构。重构之后的代码如下所示:
package com.core.state;
public enum State {
SMALL, SUPER, CAPE, FIRE
}
package com.core.state;
public interface IMario { //所有状态类的接口
State getName();
//以下是定义的事件
// 获得蘑菇
void obtainMushRoom(MarioStateMachine stateMachine);
// 获得斗篷
void obtainCape(MarioStateMachine stateMachine);
// 获得火焰
void obtainFireFlower(MarioStateMachine stateMachine);
// 遇到怪物
void meetMonster(MarioStateMachine stateMachine);
}
package com.core.state;
public class SmallMario implements IMario {
private static final SmallMario instance = new 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...
}
}
package com.core.state;
public class SuperMario implements IMario {
private static final SuperMario instance = new SuperMario();
public static SuperMario getInstance() {
return instance;
}
@Override
public State getName() {
return State.SUPER;
}
@Override
public void obtainMushRoom(MarioStateMachine stateMachine) {
// do nothing...
}
@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);
}
}
package com.core.state;
public class CapeMario implements IMario {
private static final CapeMario instance = new CapeMario();
public static CapeMario getInstance() {
return instance;
}
@Override
public State getName() {
return State.CAPE;
}
@Override
public void obtainMushRoom(MarioStateMachine stateMachine) {
// do nothing...
}
@Override
public void obtainCape(MarioStateMachine stateMachine) {
// do nothing...
}
@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() - 200);
}
}
package com.core.state;
public class FireMario implements IMario {
private static final FireMario instance = new FireMario();
public static FireMario getInstance() {
return instance;
}
@Override
public State getName() {
return State.FIRE;
}
@Override
public void obtainMushRoom(MarioStateMachine stateMachine) {
// do nothing...
}
@Override
public void obtainCape(MarioStateMachine stateMachine) {
// do nothing...
}
@Override
public void obtainFireFlower(MarioStateMachine stateMachine) {
// do nothing...
}
@Override
public void meetMonster(MarioStateMachine stateMachine) {
stateMachine.setCurrentState(SmallMario.getInstance());
stateMachine.setScore(stateMachine.getScore() - 300);
}
}
package com.core.state;
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;
}
}
public class FiniteStateMachineImproveDemo {
public static void main(String[] args) {
MarioStateMachine machine = new MarioStateMachine();
machine.obtainMushRoom();
System.out.println(machine.getCurrentState()); //SUPER
machine.obtainFireFlower();
System.out.println(machine.getCurrentState());//FIRE
machine.obtainCape();
System.out.println(machine.getCurrentState()); //FIRE
}
}
实际上,像游戏这种比较复杂的状态机,包含的状态比较多,优先考虑使用查表法,而状态模式会引入非常多的状态类,会导致代码比较难维护。相反,像电商下单、外卖下单这种类型的状态机,它们的状态并不多,状态转移也比较简单,但事件触发执行的动作包含的业务逻辑可能会比较复杂,所以,更加推荐使用状态模式来实现。
2 电商积分抽奖
APP抽奖问题
请编写程序完成APP抽奖活动,具体要求如下
- 假如每参见一次这个活动要扣除用户50积分,中奖概率时10%
- 奖品数量固定,抽完就不能抽
- 活动有四个状态:可以抽奖,不能抽奖,发放奖品和奖品领完
- 活动的四个状态转换关系图
状态模式的基本介绍
-
状态模式(State Pattern):它主要用来解决对象在多个状态转换时,需要对外输出不同的行文的问题。状态和行为是一一对应的,状态之间可以相互转换。
-
当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类
状态模式的类图
-
Context类为环境角色,用户维护State实例,,这个实例定义了当前状态
-
State是抽象状态角色,定义一个接口封装与Context的一个特点接口相关行为
-
ConcreteState 具体的状态角色,每个子类实现一个与Context的一个状态相关行为
package com.evan.state;
public abstract class State {
// 扣除积分 -50
public abstract void deductMoney();
// 是否抽中奖
public abstract boolean raffle();
// 发放奖品
public abstract void dispensePrize();
}
package com.evan.state;
/**
* 不可以抽奖的状态
*/
public class NoRaffleState extends State {
// 初始化时传入活动引用,扣除积分后改变其状态
RaffleActivity activity;
public NoRaffleState(RaffleActivity activity) {
this.activity = activity;
}
/**
* 当前状态可以扣除积分,扣除后,将状态设置成可以抽奖的状态
*/
@Override
public void deductMoney() {
System.out.println("扣除50积分成功,可以抽奖了");
activity.setState(activity.getCanRaffleState());
}
// 当前状态不可以抽奖
@Override
public boolean raffle() {
System.out.println("扣除积分后才可以抽奖");
return false;
}
// 当前状态不可以发放奖品
@Override
public void dispensePrize() {
System.out.println("不能发放奖品");
}
}
package com.evan.state;
/**
* 抽奖活动
*/
public class RaffleActivity {
//State 表示活动当前的状态,是变化的
State state = null;
// 奖品数量
int count = 0;
// 四个属性,表示四种状态
State noRaffleState = new NoRaffleState(this);
State canRaffleState = new CanRaffleState(this);
State dispenseState = new DispenseState(this);
State dispenseOutState = new DispenseOutState(this);
/**
* 1. 初始化当前的状态为noRaffleState
* 2. 初始化奖品数量
* @param count
*/
public RaffleActivity(int count){
this.state = getNoRaffleState();
this.count = count;
}
// 扣分,调用当前状态的deductMoney
public void debuctMoney(){
state.deductMoney();
}
// 抽奖
public void raffle(){
// 如果当前的状态是抽奖成功
if(state.raffle()){
// 领取奖品
state.dispensePrize();
}
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
public int getCount() {
int current = count;
count --;
return current;
}
public void setCount(int count) {
this.count = count;
}
public State getNoRaffleState() {
return noRaffleState;
}
public void setNoRaffleState(State noRaffleState) {
this.noRaffleState = noRaffleState;
}
public State getCanRaffleState() {
return canRaffleState;
}
public void setCanRaffleState(State canRaffleState) {
this.canRaffleState = canRaffleState;
}
public State getDispenseOutState() {
return dispenseOutState;
}
public void setDispenseOutState(State dispenseOutState) {
this.dispenseOutState = dispenseOutState;
}
public State getDispenseState() {
return dispenseState;
}
public void setDispenseState(State dispenseState) {
this.dispenseState = dispenseState;
}
}
package com.evan.state;
import java.util.Random;
/**
* 可以抽奖的状态
*/
public class CanRaffleState extends State {
RaffleActivity activity;
public CanRaffleState(RaffleActivity activity) {
this.activity = activity;
}
/**
* 已经扣除过积分,不能再扣
*/
@Override
public void deductMoney() {
System.out.println("已经扣除过积分,不能再扣");
}
/**
* 可以抽奖,抽奖后根据情况,改变新的状态
*
* @return
*/
@Override
public boolean raffle() {
System.out.println("正在抽奖,请稍后");
Random random = new Random();
int i = random.nextInt(10);
// 10%的中奖机会
if (i == 0){
// 改变活动状态为发放奖品
activity.setState(activity.getDispenseState());
return true;
}else {
System.out.println("很遗憾,你没有中奖");
activity.setState(activity.getNoRaffleState());
return false;
}
}
/**
* 不能发放奖品
*/
@Override
public void dispensePrize() {
System.out.println("没有中奖,不能发放奖品");
}
}
package com.evan.state;
public class DispenseState extends State {
// 初始化时传入活动引用,扣除积分后改变其状态
RaffleActivity activity;
public DispenseState(RaffleActivity activity) {
this.activity = activity;
}
/**
* 当前状态可以扣除积分,扣除后,将状态设置成可以抽奖的状态
*/
@Override
public void deductMoney() {
System.out.println("不能扣除积分");
}
@Override
public boolean raffle() {
System.out.println("不能抽奖");
return false;
}
// 发放奖品
@Override
public void dispensePrize() {
if (activity.getCount() > 0) {
System.out.println("恭喜你中奖了");
// 改变状态不能抽奖
activity.setState(activity.getNoRaffleState());
} else {
System.out.println("把人家的积分还给人家");
System.out.println("很遗憾,奖品发完了");
// 改变状态为奖品发放完毕,抽奖end
activity.setState(activity.getDispenseOutState());
System.exit(0);
}
}
}
package com.evan.state;
//奖品发放完毕的状态
// 当activity改为 DispenseOutState ,抽奖活动结束
public class DispenseOutState extends State {
RaffleActivity activity;
public DispenseOutState(RaffleActivity activity) {
this.activity = activity;
}
@Override
public void deductMoney() {
System.out.println("奖品发放完了,请下次再参见");
}
@Override
public boolean raffle() {
System.out.println("奖品发放完了,请下次再参见");
return false;
}
@Override
public void dispensePrize() {
System.out.println("奖品发放完了,请下次再参见");
}
}
package com.evan.state;
public class Client {
public static void main(String[] args) {
// 创建一个活动
RaffleActivity activity = new RaffleActivity(1);
for (int i = 0; i < 300; i++) {
System.out.println("========第" + i + "次抽奖==========");
// 第一步扣除积分
activity.debuctMoney();
// 第二步抽奖
activity.raffle();
}
}
}
========第0次抽奖==========
扣除50积分成功,可以抽奖了
正在抽奖,请稍后
很遗憾,你没有中奖
========第1次抽奖==========
扣除50积分成功,可以抽奖了
正在抽奖,请稍后
很遗憾,你没有中奖
========第2次抽奖==========
扣除50积分成功,可以抽奖了
正在抽奖,请稍后
很遗憾,你没有中奖
========第3次抽奖==========
扣除50积分成功,可以抽奖了
正在抽奖,请稍后
恭喜你中奖了
========第4次抽奖==========
扣除50积分成功,可以抽奖了
正在抽奖,请稍后
很遗憾,你没有中奖
========第5次抽奖==========
扣除50积分成功,可以抽奖了
正在抽奖,请稍后
很遗憾,你没有中奖
========第6次抽奖==========
扣除50积分成功,可以抽奖了
正在抽奖,请稍后
很遗憾,你没有中奖
========第7次抽奖==========
扣除50积分成功,可以抽奖了
正在抽奖,请稍后
很遗憾,你没有中奖
========第8次抽奖==========
扣除50积分成功,可以抽奖了
正在抽奖,请稍后
很遗憾,你没有中奖
========第9次抽奖==========
扣除50积分成功,可以抽奖了
正在抽奖,请稍后
很遗憾,你没有中奖
========第10次抽奖==========
扣除50积分成功,可以抽奖了
正在抽奖,请稍后
很遗憾,你没有中奖
========第11次抽奖==========
扣除50积分成功,可以抽奖了
正在抽奖,请稍后
很遗憾,你没有中奖
========第12次抽奖==========
扣除50积分成功,可以抽奖了
正在抽奖,请稍后
很遗憾,奖品发完了
状态模式是状态机的一种实现方法。它通过将事件触发的状态转移和动作执行,拆分到不同的状态类中,以此来避免状态机类中的分支判断逻辑,应对状态机类代码的复杂性。