您现在的位置是:首页 >技术交流 >设计模式之【状态模式】,如何设计一个“状态管理大师”网站首页技术交流

设计模式之【状态模式】,如何设计一个“状态管理大师”

秃了也弱了。 2024-06-19 13:56:19
简介设计模式之【状态模式】,如何设计一个“状态管理大师”

一、什么是状态模式

状态模式(State Pattern)也称为状态机模式(State Machine Pattern),是允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类,属于行为型模式。

状态模式中类的行为是由状态决定的,不同的状态下有不同的行为。其意图是让一个对象在其内部改变的时候,其行为也随之改变。

状态模式核心是状态与行为绑定,不同的状态对应不同的行为。

1、状态模式使用场景

状态模式在生活场景中也比较常见。比如:网购订单状态的变化;坐电梯时电梯状态的变化等等。

在软件开发过程中,对于某一项操作,可能存在不同的情况。通常处理多情况问题最直接的方式就是使用if-else或者switch-case条件语句进行枚举。但是这种做法对于复杂状态的判断天然存在弊端:条件判断语句过于臃肿,可读性差,且不具备扩展性,维护难度也大。而如果转换思维,将这些不同状态独立起来用各个不同的类进行表示,系统处于哪种情况,直接使用相应的状态类对象进行处理,消除了if-else和switch-case等冗余语句,代码更具有层次性且具备良好扩展力。

状态模式主要解决的就是当控制一个对象状态的条件表达式过于复杂时的情况。通过把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。对象的行为依赖于它的状态(属性),并且会根据它的状态改变而改变它的相关行为。

状态模式适用于以下场景:

  • 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。;
  • 一个操作中含有庞大的多分支结构,并且这些分支取决于对象的状态。

2、状态模式优缺点

优点:

  • 结构清晰:将状态独立为类,消除了冗余的if-else或switch-case语句,使代码更加简洁,提高系统可维护性;
  • 将状态转换显示化:通常的对象内部都是使用数值类型来定义状态,状态的切换是通过赋值进行表现,不够直观;而使用状态类,在切换状态时,是以不同的类进行表示,转换目的更加明确;
  • 状态类职责明确且具备扩展性。

缺点:

  • 类膨胀:如果一个事物具备很多状态,则会造成状态类太多;
  • 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码混乱;
  • 状态模式对开闭原则的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改对应类的源代码。

3、状态模式的三大角色

在这里插入图片描述
状态模式包含以下主要角色。

  • 环境(Context)角色:也称为上下文,它定义了客户程序需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
  • 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。
  • 具体状态(Concrete State)角色:实现抽象状态所对应的行为,并且在需要的情况下进行状态切换。

4、状态模式与责任链模式的区别

状态模式和责任链模式都能消除if分支过多的问题。但某些情况下,状态模式中的状态可以理解为责任,那么这种情况下,两种模式都可以使用。

从定义来看,状态模式强调的是一个对象内在状态的改变,而责任链模式强调的是外部节点对象间的改变。

从其代码实现上来看,他们间最大的区别就是状态模式各个状态对象知道自己下一个要进入的状态对象;而责任链模式并不清楚其下一个节点处理对象,因为链式组装由客户端负责。

5、状态模式与策略模式的区别

状态模式和策略模式的UML类图架构几乎完全一样,但他们的应用场景是不一样的。策略模式多种算法行为择其一都能满足,彼此之间是独立的,用户可自行更换策略算法;而状态模式各个状态间是存在相互关系的,彼此之间在一定条件下存在自动切换状态效果,且用户无法指定状态,只能设置初始状态。

二、实例

1、状态模式的一般写法

(1)简单写法

//抽象状态:State
public interface IState {
    void handle();
}
//具体状态类
public class ConcreteStateA implements IState {
    public void handle() {
        //必要时刻需要进行状态切换
        System.out.println("StateA do action");
    }
}
//具体状态类
public class ConcreteStateB implements IState {
    public void handle() {
        //必要时刻需要进行状态切换
        System.out.println("StateB do action");
    }
}
//环境类
public class Context {
    private static final IState STATE_A = new ConcreteStateA();
    private static final IState STATE_B = new ConcreteStateB();
    //默认状态A
    private IState currentState = STATE_A;

    public void setState(IState state) {
        this.currentState = state;
    }

    public void handle() {
        this.currentState.handle();
    }
}
// 测试类
public class Test {
    public static void main(String[] args) {
        Context context = new Context();
        context.setState(new ConcreteStateB());
        context.handle();
    }
}

(2)进阶写法

// 抽象状态:State
public abstract class State {
    protected Context context;

    public void setContext(Context context) {
        this.context = context;
    }

    public abstract void handle();
}
// 具体状态类
public class ConcreteStateA extends State {
    @Override
    public void handle() {
        System.out.println("StateA do action");
        // A状态完成后自动切换到B状态
        this.context.setState(Context.STATE_B);
        this.context.getState().handle();
    }
}
//具体状态类
public class ConcreteStateB extends State {
    @Override
    public void handle() {
        System.out.println("StateB do action");
    }
}
//环境类
public class Context {
    public static final State STATE_A = new ConcreteStateA();
    public static final State STATE_B = new ConcreteStateB();
    // 默认状态A
    private State currentState = STATE_A;
    {
        STATE_A.setContext(this);
        STATE_B.setContext(this);
    }

    public void setState(State state) {
        this.currentState = state;
        this.currentState.setContext(this);
    }

    public State getState() {
        return this.currentState;
    }

    public void handle() {
        this.currentState.handle();
    }
}
// 测试类
public class Test {
    public static void main(String[] args) {
        Context context = new Context();
        context.setState(new ConcreteStateA());
        context.handle();
    }
}

(3)一般写法

// 状态接口
public interface State {
   public void doAction(Context context);
}
// 具体状态类
public class StartState implements State {
 
   public void doAction(Context context) {
      System.out.println("Player is in start state");
      context.setState(this); 
   }
 
   public String toString(){
      return "Start State";
   }
}

// 具体状态类
public class StopState implements State {
 
   public void doAction(Context context) {
      System.out.println("Player is in stop state");
      context.setState(this); 
   }
 
   public String toString(){
      return "Stop State";
   }
}
// 环境类
public class Context {
   private State state;
 
   public Context(){
      state = null;
   }
 
   public void setState(State state){
      this.state = state;     
   }
 
   public State getState(){
      return state;
   }
}
// 测试
public class StatePatternDemo {
   public static void main(String[] args) {
      Context context = new Context();
 
      StartState startState = new StartState();
      startState.doAction(context);
 
      System.out.println(context.getState().toString());
 
      StopState stopState = new StopState();
      stopState.doAction(context);
 
      System.out.println(context.getState().toString());
   }
}

2、电梯状态实例

通过按钮来控制一个电梯的状态,一个电梯有开门状态,关门状态,停止状态,运行状态。每一种状态改变,都有可能要根据其他状态来更新处理。例如,如果电梯门现在处于运行时状态,就不能进行开门操作,而如果电梯门是停止状态,就可以执行开门操作。

类图如下:
在这里插入图片描述

public interface ILift {
	//电梯的4个状态
	//开门状态
	public final static int OPENING_STATE = 1;
	//关门状态
	public final static int CLOSING_STATE = 2;
	//运行状态
	public final static int RUNNING_STATE = 3;
	//停止状态
	public final static int STOPPING_STATE = 4;
	//设置电梯的状态
	public void setState(int state);
	//电梯的动作
	public void open();
	public void close();
	public void run();
	public void stop();
}

public class Lift implements ILift {
	private int state;
	@Override
	public void setState(int state) {
		this.state = state;
	}
	//执行关门动作
	@Override
	public void close() {
		switch (this.state) {
			case OPENING_STATE:
			System.out.println("电梯关门了。。。");//只有开门状态可以关闭电梯
			门,可以对应电梯状态表来看
			this.setState(CLOSING_STATE);//关门之后电梯就是关闭状态了
			break;
			case CLOSING_STATE:
			//do nothing //已经是关门状态,不能关门
			break;
			case RUNNING_STATE:
			//do nothing //运行时电梯门是关着的,不能关门
			break;
			case STOPPING_STATE:
			//do nothing //停止时电梯也是关着的,不能关门
			break;
		}
	}
	//执行开门动作
	@Override
	public void open() {
		switch (this.state) {
			case OPENING_STATE://门已经开了,不能再开门了
			//do nothing
			break;
			case CLOSING_STATE://关门状态,门打开:
			System.out.println("电梯门打开了。。。");
			this.setState(OPENING_STATE);
			break;
			case RUNNING_STATE:
			//do nothing 运行时电梯不能开门
			break;
			case STOPPING_STATE:
			System.out.println("电梯门开了。。。");//电梯停了,可以开门了
			this.setState(OPENING_STATE);
			break;
		}
	}
	//执行运行动作
	@Override
	public void run() {
		switch (this.state) {
			case OPENING_STATE://电梯不能开着门就走
			//do nothing
			break;
			case CLOSING_STATE://门关了,可以运行了
			System.out.println("电梯开始运行了。。。");
			this.setState(RUNNING_STATE);//现在是运行状态
			break;
			case RUNNING_STATE:
			//do nothing 已经是运行状态了
			break;
			case STOPPING_STATE:
			System.out.println("电梯开始运行了。。。");
			this.setState(RUNNING_STATE);
			break;
		}
	}
	//执行停止动作
	@Override
	public void stop() {
		switch (this.state) {
			case OPENING_STATE: //开门的电梯已经是是停止的了(正常情况下)
			//do nothing
			break;
			case CLOSING_STATE://关门时才可以停止
			System.out.println("电梯停止了。。。");
			this.setState(STOPPING_STATE);
			break;
			case RUNNING_STATE://运行时当然可以停止了
			System.out.println("电梯停止了。。。");
			this.setState(STOPPING_STATE);
			break;
			case STOPPING_STATE:
			//do nothing
			break;
		}
	}
}
public class Client {
	public static void main(String[] args) {
	Lift lift = new Lift();
	lift.setState(ILift.STOPPING_STATE);//电梯是停止的
	lift.open();//开门
	lift.close();//关门
	lift.run();//运行
	lift.stop();//停止
	}
}

上面的代码使用了大量的switch…case这样的判断(if…else也是一样),使程序的可阅读性变差。并且扩展性很差。如果新加了断电的状态,我们需要修改上面判断逻辑。

优化:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
对上述电梯的案例使用状态模式进行改进。类图如下:
在这里插入图片描述

//抽象状态类
public abstract class LiftState {
	//定义一个环境角色,也就是封装状态的变化引起的功能变化
	protected Context context;
	public void setContext(Context context) {
		this.context = context;
	}
	//电梯开门动作
	public abstract void open();
	//电梯关门动作
	public abstract void close();
	//电梯运行动作
	public abstract void run();
	//电梯停止动作
	public abstract void stop();
}
//开启状态
public class OpenningState extends LiftState {
	//开启当然可以关闭了,我就想测试一下电梯门开关功能
	@Override
	public void open() {
		System.out.println("电梯门开启...");
	}
	@Override
	public void close() {
		//状态修改
		super.context.setLiftState(Context.closeingState);
		//动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作
		super.context.getLiftState().close();
	}
	//电梯门不能开着就跑,这里什么也不做
	@Override
	public void run() {
		//do nothing
	}
	//开门状态已经是停止的了
	@Override
	public void stop() {
		//do nothing
	}
}

//运行状态
public class RunningState extends LiftState {
	//运行的时候开电梯门?你疯了!电梯不会给你开的
	@Override
	public void open() {
		//do nothing
	}
	//电梯门关闭?这是肯定了
	@Override
	public void close() {//虽然可以关门,但这个动作不归我执行
		//do nothing
	}
	//这是在运行状态下要实现的方法
	@Override
	public void run() {
		System.out.println("电梯正在运行...");
	}
	//这个事绝对是合理的,光运行不停止还有谁敢做这个电梯?!估计只有上帝了
	@Override
	public void stop() {
		super.context.setLiftState(Context.stoppingState);
		super.context.stop();
	}
}

//停止状态
public class StoppingState extends LiftState {
	//停止状态,开门,那是要的!
	@Override
	public void open() {
		//状态修改
		super.context.setLiftState(Context.openningState);
		//动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作
		super.context.getLiftState().open();
	}
	@Override
	public void close() {//虽然可以关门,但这个动作不归我执行
		//状态修改
		super.context.setLiftState(Context.closeingState);
		//动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作
		super.context.getLiftState().close();
	}
	//停止状态再跑起来,正常的很
	@Override
	public void run() {
		//状态修改
		super.context.setLiftState(Context.runningState);
		//动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作
		super.context.getLiftState().run();
	}
	//停止状态是怎么发生的呢?当然是停止方法执行了
	@Override
	public void stop() {
		System.out.println("电梯停止了...");
	}
}

//关闭状态
public class ClosingState extends LiftState {
	@Override
	//电梯门关闭,这是关闭状态要实现的动作
	public void close() {
		System.out.println("电梯门关闭...");
	}
	//电梯门关了再打开,逗你玩呢,那这个允许呀
	@Override
	public void open() {
		super.context.setLiftState(Context.openningState);
		super.context.open();
	}
	//电梯门关了就跑,这是再正常不过了
	@Override
	public void run() {
		super.context.setLiftState(Context.runningState);
		super.context.run();
	}
	//电梯门关着,我就不按楼层
	@Override
	public void stop() {
		super.context.setLiftState(Context.stoppingState);
		super.context.stop();
	}
}
//环境角色
public class Context {
	//定义出所有的电梯状态
	public final static OpenningState openningState = new OpenningState();//开门状态,这时候电梯只能关闭
	public final static ClosingState closeingState = new ClosingState();// 关闭状态,这时候电梯可以运行、停止和开门
	public final static RunningState runningState = new RunningState();// 运行状态,这时候电梯只能停止
	public final static StoppingState stoppingState = new StoppingState();//停止状态,这时候电梯可以开门、运行
	//定义一个当前电梯状态
	private LiftState liftState;
	public LiftState getLiftState() {
		return this.liftState;
	}
	public void setLiftState(LiftState liftState) {
		//当前环境改变
		this.liftState = liftState;
		//把当前的环境通知到各个实现类中
		this.liftState.setContext(this);
	}
	public void open() {
		this.liftState.open();
	}
	public void close() {
		this.liftState.close();
	}
	public void run() {
		this.liftState.run();
	}
	public void stop() {
		this.liftState.stop();
	}
}
//测试类
public class Client {
	public static void main(String[] args) {
		Context context = new Context();
		context.setLiftState(new ClosingState());
		context.open();
		context.close();
		context.run();
		context.stop();
	}
}

3、阅读文章案例

我们在CSDN阅读文章时,如果觉得文章写得好,我们会评论收藏加点赞,如果处于登录状态,我们可以直接做这些行为,否则需要跳转到登录界面,登陆后再执行后续动作。

这里涉及两个状态:登录和未登录,行为有两种:评论和收藏。

// 抽象状态类
public abstract class UserState {
    protected AppContext context;

    public void setContext(AppContext context) {
        this.context = context;
    }

    public abstract void favorite();

    public abstract void comment(String comment);
}

// 具体状态类:未登录
public class UnLoginState extends UserState {

    @Override
    public void favorite() {
        this.switch2login();
        this.context.getState().favorite();
    }

    @Override
    public void comment(String comment) {
        this.switch2login();
        this.context.getState().comment(comment);
    }

    private void switch2login(){
        System.out.println("跳转到登录页!");
        this.context.setState(this.context.STATE_LOGIN);
    }
}
// 具体状态类:登录
public class LoginState extends UserState {
    @Override
    public void favorite() {
        System.out.println("收藏成功!");
    }

    @Override
    public void comment(String comment) {
        System.out.println(comment);
    }
}

// 上下文状态管理
public class AppContext {

    public static final UserState STATE_LOGIN = new LoginState();
    public static final UserState STATE_UNLOGIN = new UnLoginState();

    private UserState currentState = STATE_UNLOGIN;

    {
        STATE_LOGIN.setContext(this);
        STATE_UNLOGIN.setContext(this);
    }

    public void setState(UserState state){
        this.currentState = state;
    }

    public UserState getState(){
        return this.currentState;
    }

    public void favorite(){
        this.currentState.favorite();
    }

    public void comment(String comment){
        this.currentState.comment(comment);
    }
}

// 测试类
public class Test {
    public static void main(String[] args) {
        AppContext context = new AppContext();
        context.favorite();
        context.comment("评论:好文章,360个赞");
    }
}

4、状态机的实现案例

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

在超级马里奥游戏中,马里奥可以变身为多种形态,比如小马里奥(Small Mario)、超级马里奥(Super Mario)、火焰马里奥(Fire Mario)、斗篷马里奥(Cape Mario)等等。实际上,马里奥形态的转变就是一个状态机。其中,马里奥的不同形态就是状态机中的“状态”,游戏情节(比如吃了蘑菇)就是状态机中的“事件”,加减积分就是状态机中的“动作”。比如,吃蘑菇这个事件,会触发状态的转移:从小马里奥转移到超级马里奥,以及触发动作的执行(增加 100 积分)。

为了方便接下来的讲解,我对游戏背景做了简化,只保留了部分状态和事件。简化之后的状态转移如下图所示:
在这里插入图片描述
写了一个骨架代码,如下所示。其中,obtainMushRoom()、obtainCape()、obtainFireFlower()、meetMonster() 这几个函数,能够根据当前的状态和事件,更新状态和增减积分。

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);
  }
}

(1)状态机实现方式一:分支逻辑法

最简单直接的实现方式是,参照状态转移图,将每一个状态转移,原模原样地直译成代码。这样编写的代码会包含大量的 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。

(2)状态机实现方式二:查表法

状态机还可以用二维表来表示,如下所示。在这个二维表中,第一维表示当前状态,第二维表示事件,值表示当前状态经过事件之后,转移到的新状态及其执行的动作。
在这里插入图片描述
相对于分支逻辑的实现方式,查表法的代码实现更加清晰,可读性和可维护性更好。当修改状态机时,我们只需要修改 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 就能表示,二维数组中的值表示积分的加减值。但是,如果要执行的动作并非这么简单,而是一系列复杂的逻辑操作(比如加减积分、写数据库,还有可能发送消息通知等等),我们就没法用如此简单的二维数组来表示了。这也就是说,查表法的实现方式有一定局限性。

(3)状态机实现方式三:状态模式

虽然分支逻辑的实现方式不存在这个问题,但它又存在前面讲到的其他问题,比如分支判断逻辑较多,导致代码可读性和可维护性不好等。实际上,针对分支逻辑法存在的问题,我们可以使用状态模式来解决。

状态模式通过将事件触发的状态转移和动作执行,拆分到不同的状态类中,来避免分支判断逻辑。我们还是结合代码来理解这句话。

public interface IMario { //所有状态类的接口
  State getName();
  //以下是定义的事件
  void obtainMushRoom();
  void obtainCape();
  void obtainFireFlower();
  void meetMonster();
}
public 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...
  }
}
public 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);
  }
}
// 省略CapeMario、FireMario类...
public 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 传递进状态类。

public interface IMario {
  State getName();
  void obtainMushRoom(MarioStateMachine stateMachine);
  void obtainCape(MarioStateMachine stateMachine);
  void obtainFireFlower(MarioStateMachine stateMachine);
  void meetMonster(MarioStateMachine stateMachine);
}
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...
  }
}
// 省略SuperMario、CapeMario、FireMario类...
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;
  }
}

实际上,像游戏这种比较复杂的状态机,包含的状态比较多,我优先推荐使用查表法,而状态模式会引入非常多的状态类,会导致代码比较难维护。相反,像电商下单、外卖下单这种类型的状态机,它们的状态并不多,状态转移也比较简单,但事件触发执行的动作包含的业务逻辑可能会比较复杂,所以,更加推荐使用状态模式来实现。

5、spring-statemachine使用

状态机是状态模式的一种应用,相当于上下文角色的一个升级版。在工作流或游戏等各种系统中有大量使用,如各种工作流引擎,它几乎是状态机的子集和实现,封装状态的变化规则。

Spring提供了一个很好的状态机解决方案,就是StateMachine(状态机)。状态机帮主开发者简化状态控制的开发过程,让状态机结构更加层次化。下面,我们使用Spring状态机模拟一个订单状态流转的过程。

(1)添加依赖

<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-core</artifactId>
    <version>2.0.1.RELEASE</version>
</dependency>
// 订单实体
public class Order {
    private int id;
    private OrderStatus status;
    public void setStatus(OrderStatus status) {
        this.status = status;
    }

    public OrderStatus getStatus() {
        return status;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    @Override
    public String toString() {
        return "订单号:" + id + ", 订单状态:" + status;
    }
}

/**
 * 订单状态
 */
public enum OrderStatus {
    // 待支付,待发货,待收货,订单结束
    WAIT_PAYMENT, WAIT_DELIVER, WAIT_RECEIVE, FINISH;
}
/**
 * 订单状态改变事件
 */
public enum OrderStatusChangeEvent {
    // 支付,发货,确认收货
    PAYED, DELIVERY, RECEIVED;
}

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.StateMachineContext;
import org.springframework.statemachine.StateMachinePersist;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.persist.DefaultStateMachinePersister;
import org.springframework.statemachine.support.DefaultStateMachineContext;

import java.util.EnumSet;

/**
 * 订单状态机配置
 */
@Configuration
@EnableStateMachine(name = "orderStateMachine")
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStatus, OrderStatusChangeEvent> {
 
    /**
     * 配置状态
     * @param states
     * @throws Exception
     */
    public void configure(StateMachineStateConfigurer<OrderStatus, OrderStatusChangeEvent> states) throws Exception {
        states
                .withStates()
                .initial(OrderStatus.WAIT_PAYMENT)
                .states(EnumSet.allOf(OrderStatus.class));
    }
 
    /**
     * 配置状态转换事件关系
     * @param transitions
     * @throws Exception
     */
    public void configure(StateMachineTransitionConfigurer<OrderStatus, OrderStatusChangeEvent> transitions) throws Exception {
        transitions
                .withExternal().source(OrderStatus.WAIT_PAYMENT).target(OrderStatus.WAIT_DELIVER).event(OrderStatusChangeEvent.PAYED)
                .and()
                .withExternal().source(OrderStatus.WAIT_DELIVER).target(OrderStatus.WAIT_RECEIVE).event(OrderStatusChangeEvent.DELIVERY)
                .and()
                .withExternal().source(OrderStatus.WAIT_RECEIVE).target(OrderStatus.FINISH).event(OrderStatusChangeEvent.RECEIVED);
    }
 
    /**
     * 持久化配置
     * 实际使用中,可以配合redis等,进行持久化操作
     * @return
     */
    @Bean
    public DefaultStateMachinePersister persister(){
        return new DefaultStateMachinePersister<>(new StateMachinePersist<Object, Object, Order>() {
            @Override
            public void write(StateMachineContext<Object, Object> context, Order order) throws Exception {
                //此处并没有进行持久化操作
            }
 
            @Override
            public StateMachineContext<Object, Object> read(Order order) throws Exception {
                //此处直接获取order中的状态,其实并没有进行持久化读取操作
                return new DefaultStateMachineContext(order.getStatus(), null, null, null);
            }
        });
    }
}
import org.springframework.messaging.Message;
import org.springframework.statemachine.annotation.OnTransition;
import org.springframework.statemachine.annotation.WithStateMachine;
import org.springframework.stereotype.Component;

@Component("orderStateListener")
@WithStateMachine(name = "orderStateMachine")
public class OrderStateListenerImpl{
 
    @OnTransition(source = "WAIT_PAYMENT", target = "WAIT_DELIVER")
    public boolean payTransition(Message<OrderStatusChangeEvent> message) {
        Order order = (Order) message.getHeaders().get("order");
        order.setStatus(OrderStatus.WAIT_DELIVER);
        System.out.println("支付,状态机反馈信息:" + message.getHeaders().toString());
        return true;
    }
 
    @OnTransition(source = "WAIT_DELIVER", target = "WAIT_RECEIVE")
    public boolean deliverTransition(Message<OrderStatusChangeEvent> message) {
        Order order = (Order) message.getHeaders().get("order");
        order.setStatus(OrderStatus.WAIT_RECEIVE);
        System.out.println("发货,状态机反馈信息:" + message.getHeaders().toString());
        return true;
    }
 
    @OnTransition(source = "WAIT_RECEIVE", target = "FINISH")
    public boolean receiveTransition(Message<OrderStatusChangeEvent> message){
        Order order = (Order) message.getHeaders().get("order");
        order.setStatus(OrderStatus.FINISH);
        System.out.println("收货,状态机反馈信息:" + message.getHeaders().toString());
        return true;
    }
}
// service接口
public interface IOrderService {
    //创建新订单
    Order create();
    //发起支付
    Order pay(int id);
    //订单发货
    Order deliver(int id);
    //订单收货
    Order receive(int id);
    //获取所有订单信息
    Map<Integer, Order> getOrders();
}

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.persist.StateMachinePersister;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Service("orderService")
public class OrderServiceImpl implements IOrderService {

    @Autowired
    private StateMachine<OrderStatus, OrderStatusChangeEvent> orderStateMachine;
 
    @Autowired
    private StateMachinePersister<OrderStatus, OrderStatusChangeEvent, Order> persister;
 
    private int id = 1;
    private Map<Integer, Order> orders = new HashMap<>();

    public Order create() {
        Order order = new Order();
        order.setStatus(OrderStatus.WAIT_PAYMENT);
        order.setId(id++);
        orders.put(order.getId(), order);
        return order;
    }

    public Order pay(int id) {
        Order order = orders.get(id);
        System.out.println("线程名称:" + Thread.currentThread().getName() + " 尝试支付,订单号:" + id);
        Message message = MessageBuilder.withPayload(OrderStatusChangeEvent.PAYED).setHeader("order", order).build();
        if (!sendEvent(message, order)) {
            System.out.println("线程名称:" + Thread.currentThread().getName() + " 支付失败, 状态异常,订单号:" + id);
        }
        return orders.get(id);
    }

    public Order deliver(int id) {
        Order order = orders.get(id);
        System.out.println("线程名称:" + Thread.currentThread().getName() + " 尝试发货,订单号:" + id);
        if (!sendEvent(MessageBuilder.withPayload(OrderStatusChangeEvent.DELIVERY).setHeader("order", order).build(), orders.get(id))) {
            System.out.println("线程名称:" + Thread.currentThread().getName() + " 发货失败,状态异常,订单号:" + id);
        }
        return orders.get(id);
    }

    public Order receive(int id) {
        Order order = orders.get(id);
        System.out.println("线程名称:" + Thread.currentThread().getName() + " 尝试收货,订单号:" + id);
        if (!sendEvent(MessageBuilder.withPayload(OrderStatusChangeEvent.RECEIVED).setHeader("order", order).build(), orders.get(id))) {
            System.out.println("线程名称:" + Thread.currentThread().getName() + " 收货失败,状态异常,订单号:" + id);
        }
        return orders.get(id);
    }
 

    public Map<Integer, Order> getOrders() {
        return orders;
    }
 
 
    /**
     * 发送订单状态转换事件
     *
     * @param message
     * @param order
     * @return
     */
    private synchronized boolean sendEvent(Message<OrderStatusChangeEvent> message, Order order) {
        boolean result = false;
        try {
            orderStateMachine.start();
            //尝试恢复状态机状态
            persister.restore(orderStateMachine, order);
            //添加延迟用于线程安全测试
            Thread.sleep(1000);
            result = orderStateMachine.sendEvent(message);
            //持久化状态机状态
            persister.persist(orderStateMachine, order);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            orderStateMachine.stop();
        }
        return result;
    }
}
// 测试代码
@SpringBootApplication
public class Test {
    public static void main(String[] args) {

        Thread.currentThread().setName("主线程");

        ConfigurableApplicationContext context = SpringApplication.run(Test.class,args);

        IOrderService orderService = (IOrderService)context.getBean("orderService");

        orderService.create();
        orderService.create();

        orderService.pay(1);

        new Thread("客户线程"){
            @Override
            public void run() {
                orderService.deliver(1);
                orderService.receive(1);
            }
        }.start();

        orderService.pay(2);
        orderService.deliver(2);
        orderService.receive(2);

        System.out.println("全部订单状态:" + orderService.getOrders());

    }
}

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。