状态模式

144 阅读14分钟

什么是有限状态机?

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

对于刚刚给出的状态机的定义,结合一个具体的例子,来进一步解释一下。

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

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

为了方便接下来的讲解,对游戏背景做了简化,只保留了部分状态和事件。简化之后的状态转移如下图所示:

image.png

  • 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)
smallSuper/+100cape/+200Fire/+300/
super/cape/+200Fire/+300small/-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抽奖活动,具体要求如下

  1. 假如每参见一次这个活动要扣除用户50积分,中奖概率时10%
  2. 奖品数量固定,抽完就不能抽
  3. 活动有四个状态:可以抽奖,不能抽奖,发放奖品和奖品领完
  4. 活动的四个状态转换关系图

图片

状态模式的基本介绍

  1. 状态模式(State Pattern):它主要用来解决对象在多个状态转换时,需要对外输出不同的行文的问题。状态和行为是一一对应的,状态之间可以相互转换。

  2. 当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类

状态模式的类图

图片

  1. Context类为环境角色,用户维护State实例,,这个实例定义了当前状态

  2. State是抽象状态角色,定义一个接口封装与Context的一个特点接口相关行为

  3. 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积分成功,可以抽奖了
正在抽奖,请稍后
很遗憾,奖品发完了

状态模式是状态机的一种实现方法。它通过将事件触发的状态转移和动作执行,拆分到不同的状态类中,以此来避免状态机类中的分支判断逻辑,应对状态机类代码的复杂性。