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

news2025/2/24 13:58:12

文章目录

  • 一、什么是状态模式
    • 1、状态模式使用场景
    • 2、状态模式优缺点
    • 3、状态模式的三大角色
    • 4、状态模式与责任链模式的区别
    • 5、状态模式与策略模式的区别
  • 二、实例
    • 1、状态模式的一般写法
      • (1)简单写法
      • (2)进阶写法
      • (3)一般写法
    • 2、电梯状态实例
    • 3、阅读文章案例
    • 4、状态机的实现案例
      • (1)状态机实现方式一:分支逻辑法
      • (2)状态机实现方式二:查表法
      • (3)状态机实现方式三:状态模式
    • 5、spring-statemachine使用

一、什么是状态模式

状态模式(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());

    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/558452.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

redis的优化和持久化

持久化的功能&#xff1a;Redis是内存数据库&#xff0c;数据都是存储在内存中&#xff0c;为了避免服务器断电等原因导致Redis进程异常退出后数据的永久丢失&#xff0c;需要定期将Redis中的数据以某种形式&#xff08;数据或命令&#xff09;从内存保存到硬盘&#xff1b;当下…

在 Android 手机上恢复出厂设置后恢复照片的 4 种简单方法(新方法)

“嗨&#xff0c;谁能帮我恢复我的照片&#xff0c;因为我不小心恢复了出厂设置&#xff0c;而且我没有做备份&#xff1f;几个月来我一直试图通过使用恢复软件来恢复我的照片&#xff0c;root 了一个深扫描&#xff0c;但没用……” 恢复出厂设置可以清除电子设备的所有信息并…

【arxiv】找论文idea : 关于 SAM 的论文扫读(二)

文章目录 一、A Comprehensive Survey on Segment Anything Model for Vision and Beyond二、Segment Anything Model (SAM) Enhanced Pseudo Labels for Weakly Supervised Semantic Segmentation三、How Segment Anything Model (SAM) Boost Medical Image Segmentation?四、…

OJ万题详解––组合线段(C++详解)

好久没有更新博文了&#xff0c;我上一篇博文的发布时间还是&#xff1a; 题目 这是我们周考的第二道题&#xff0c;乍一看&#xff0c;很简单&#xff0c;仔细做&#xff0c;更简单。&#xff08;我都没做起&#xff09; 分析 我相信&#xff0c;很多人第一次看到这个题目就…

Nginx安装与使用

文章目录 1. &#x1fa90;Nginx-概述1.1 介绍1.2 下载和安装1.2.1 官网下载1.2.2 网盘下载1.2.3 安装 1.3 目录结构 2. &#x1fa90;Nginx-命令2.1 常用命令2.2 环境变量配置 3. &#x1fa90;Nginx-应用3.1 配置文件结构3.2 部署静态资源3.2.1 介绍3.2.2 测试 3.3 反向代理3…

conda 笔记 conda命令收集

1 conda相关命令 conda info 判断conda是否正常安装检查conda版本号 conda update -n base conda 将conda更新到当前版本 conda update anaconda 将所有的package尽量更新到最新的版本加尽量的原因是&#xff0c;包裹之间可能会有冲突&#xff0c;所以需要优先保证packa…

玩转ChatGPT:制作思维导图

一、写在前面 最近&#xff0c;在学习深度学习图像识别的相关知识和代码&#xff0c;想着能否用小Chat搞一个思维导图。 简单问小Chat&#xff1a; 咒语&#xff1a;我怎么使用你做一个思维导图&#xff1f;需要配合什么软件生成&#xff1f;&#xff1f; 大意就是&#xff…

JavaEE(系列11) -- 多线程案例4(线程池)

目录 1. 线程池 2. 创建线程池 2.1 Executors类 2.2 ThreadPoolExecutor类 3. 自己实现线程池 1. 线程池 线程池是一种多线程处理形式&#xff0c;处理过程中将任务添加到队列&#xff0c;然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈…

百分位数、数据分布、直方图、正态数据分布

目录 1、百分位数 2、数据分布 3、正态数据分布 1、百分位数 统计学中使用百分位数&#xff08;Percentiles&#xff09;提供一个数字&#xff0c;该数字描述了给定百分比值小于的值。 例如&#xff1a;假设我们有一个数组&#xff0c;包含一时刻一条街上人的年龄 arr […

搜索二维矩阵——力扣74

文章目录 题目描述法一&#xff09;一次二分查找法二&#xff09;两次二分查找法三&#xff09;抽象二叉搜索树BST解法 题目描述 法一&#xff09;一次二分查找 首先分析题目&#xff1a;由于①每行的整数从左到右升序&#xff1b;②每行的第一个整数>前一行的最后一个整数&…

AC和AP和STA信息查询

AC和AP和STA信息查询 1、AC的运行统计信息1.1、查看设备的部件类型及状态信息 2、AP的运行统计信息2.1、AP的运行信息 / 查看RU的运行信息2.2、AP性能统计信息2.3、AP的射频信息2.4、AP重启失败的记录2.5、非法AP的存在情况2.6、设备信道切换的记录信息2.7、查看指定AP射频上的…

自助式数据分析工具:jvs-sdw(数据智仓)图表配置说明和实现效果

图表的查询条件配置示例说明 在JVS数据分析图表中&#xff0c;是支持查询条件进行多个数据展示组件的联动查询&#xff0c;实现的方式是设置统一的查询条件&#xff0c;在每个图表组件中&#xff0c;设置对应的查询字段去关联查询条件&#xff0c;那么下面我们逐步介绍配置方式…

CMOS摄像头驱动分析

CMOS摄像头驱动分析 文章目录 CMOS摄像头驱动分析ov2640_probe_dt从设备树中获取ov2640的GPIO引脚并进行初始化v4l2_i2c_subdev_init初始化v4l2子设备v4l2_ctrl_new_std添加vflip控制器 ov2640_probe_dt从设备树中获取ov2640的GPIO引脚并进行初始化 ov2640_probe_dt从设备树中…

设计模式剖析,授之以渔(java代码)

创建型模式 简单工厂模式 public class FoodFactory {public static Food makeFood(String name) {if (name.equals("noodle")) {Food noodle new LanZhouNoodle();noodle.addSpicy("more");return noodle;} else if (name.equals("chicken")…

【论文阅读笔记】BaFFLe: Backdoor Detection via Feedback-based Federated Learning

个人阅读笔记&#xff0c;如有错误欢迎指出 会议&#xff1a;2021 IEEE 41st International Conference on Distributed Computing Systems (ICDCS) BaFFLe: Backdoor Detection via Feedback-based Federated Learning | IEEE Conference Publication | IEEE Xplore 问题&am…

泰克(Tektronix) TCP305A电流探头TCPA300电流放大器

泰克Tektronix TCP305ATCPA300电流放大器 泰克 TCP305 电流探头是一种同时使用变压器和霍尔效应技术的分芯式探头。当通过 TEKPROBE Level II、TekConnect&#xff08;带 TCA-BNC&#xff09;或 TekVPI&#xff08;带 TPA-BNC&#xff09;接口连接到泰克示波器时&#xff0c;泰…

GPU服务器安装Anaconda

1.下载Anaconda安装包&#xff0c;官网地址&#xff0c;清华源地址。 在官网下载到本地之后&#xff0c;可以通过文件传输上传安装包到服务器&#xff0c;使用清华源地址可以直接使用wget下载所需版本&#xff0c;例如&#xff1a; wget https://mirrors.tuna.tsinghua.edu.c…

Hibernate 快速入门

Hibernate 快速入门 〇、前言一、搭建 Hibernate 项目步骤1:新建 Java 项目附录1:新建Java项目中的相关文件信息步骤2:添加 Hibernate 框架支持附录2:添加Hibernate框架支持后,Java项目中的相关文件信息步骤3:其他关键配置1、添加数据库驱动包(本文以MySQL为例)2、配置…

详解C语言assert宏

前言&#xff1a;我们经常在写代码时&#xff0c;发现一些大牛的代码中总有一句assert&#xff08;表达式&#xff09;&#xff0c;经过在网上的学习&#xff0c;笔者也浅显的了解了assert的相关知识&#xff0c;assert一般用于规范代码&#xff0c;避免不必要的错误&#xff0…

【Linux高级 I/O(5)】初识存储映射 I/O——mmap()和 munmap()(附代码示例)

存储映射 I/O 存储映射 I/O&#xff08;memory-mapped I/O&#xff09;是一种基于内存区域的高级 I/O 操作&#xff0c;它能将一个文件映射到进程地址空间中的一块内存区域中&#xff0c;当从这段内存中读数据时&#xff0c;就相当于读文件中的数据&#xff08;对文件进…