【设计模式】【行为型模式】状态模式(State)

👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD

🔥 2025本人正在沉淀中... 博客更新速度++

📫 欢迎+V: flzjcsg2,我们共同讨论Java深渊的奥秘

🎵 当你的天空突然下了大雨,那是我在为你炸乌云

文章目录

一、入门

什么是状态模式?

状态模式(State Pattern)是一种行为设计模式,允许对象在其内部状态改变时改变其行为,使其看起来像是改变了类。状态模式的核心思想是将对象的状态封装成独立的类,并将行为委托给代表当前状态的对象。

为什么需要状态模式?

有这样一个场景,如果不用状态模式,那么电题的每一个动作(open、close、stop、run),都会先对状态进行判断,再决定能否执行(比如运行中的电梯是不能开门的),类图如下所示。

java 复制代码
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;
        }
    }
}

在没有使用状态模式的情况下,通常会使用大量的if-elseswitch-case语句来处理对象在不同状态下的行为。这种方式会带来以下问题:

  • 代码臃肿且难以维护 :当状态数量增加时,if-elseswitch-case语句会变得非常冗长。每次新增状态或修改状态转换逻辑时,都需要修改这些条件语句,容易引入错误。
  • 违反开闭原则:开闭原则要求软件实体(类、模块、函数等)对扩展开放,对修改关闭。使用条件语句时,新增状态或修改行为需要修改现有代码,而不是扩展。
  • 状态和行为耦合:状态和行为逻辑通常混杂在一起,导致代码难以理解和测试。如果需要复用某些状态的行为,很难将其提取出来。
  • 难以扩展:当状态转换逻辑变得复杂时(例如,某些状态的下一个状态有多个分支),条件语句会变得更加混乱。新增状态或修改状态转换逻辑时,可能会影响其他状态的逻辑。

怎样实现状态模式?

状态模式的组成

  • Context(上下文):它定义了客户程序需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
  • State(状态):定义一个接口,封装与Context的特定状态相关的行为。
  • ConcreteState(具体状态):实现State接口,定义与Context的某个状态相关的行为。

【案例】 电梯状态改进版

State(状态)LiftState

java 复制代码
//抽象状态类
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();
}

ConcreteState(具体状态)OpenningState类、RunningState类、StoppingState类和ClosingState

java 复制代码
//开启状态
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();
    }
}

Context(上下文)Context

java 复制代码
//环境角色
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();
    }
}

测试类

java 复制代码
//测试类
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();
    }
}

二、状态模式在源码中的运用

Java线程管理

Java 的线程(Thread)生命周期就是一个典型的状态模式应用。线程的状态包括NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITINGTERMINATED虽然 Java 线程的状态管理没有直接使用状态模式的设计 ,但其设计思想与状态模式非常相似。

线程状态转换:

  • NEW -> RUNNABLE(调用 start() 方法)
  • RUNNABLE -> BLOCKED(等待锁)
  • RUNNABLE -> WAITING(调用 wait() 方法)
  • RUNNABLE -> TIMED_WAITING(调用 sleep()join() 方法)
  • RUNNABLE -> TERMINATED(线程执行完毕)

如果使用状态模式来实现线程状态管理,可以这样设计:

java 复制代码
interface ThreadState {
    void start(ThreadContext context);
    void run(ThreadContext context);
    void block(ThreadContext context);
    void terminate(ThreadContext context);
}

class NewState implements ThreadState {
    @Override
    public void start(ThreadContext context) {
        context.setState(new RunnableState());
    }

    @Override
    public void run(ThreadContext context) {
        throw new IllegalStateException("线程未启动,无法运行。");
    }

    @Override
    public void block(ThreadContext context) {
        throw new IllegalStateException("线程未启动,无法阻塞。");
    }

    @Override
    public void terminate(ThreadContext context) {
        throw new IllegalStateException("线程未启动,无法终止。");
    }
}

class RunnableState implements ThreadState {
    @Override
    public void start(ThreadContext context) {
        throw new IllegalStateException("线程已启动,无需再次启动。");
    }

    @Override
    public void run(ThreadContext context) {
        System.out.println("线程正在运行...");
    }

    @Override
    public void block(ThreadContext context) {
        context.setState(new BlockedState());
    }

    @Override
    public void terminate(ThreadContext context) {
        context.setState(new TerminatedState());
    }
}

class ThreadContext {
    private ThreadState state;

    public ThreadContext() {
        this.state = new NewState();
    }

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

    public void start() {
        state.start(this);
    }

    public void run() {
        state.run(this);
    }

    public void block() {
        state.block(this);
    }

    public void terminate() {
        state.terminate(this);
    }
}

Spring 状态机(Spring State Machine)

Spring 提供了一个状态机框架(Spring State Machine),用于管理复杂的状态转换逻辑。它基于状态模式设计,支持状态、事件、转换等概念。

java 复制代码
@Configuration
@EnableStateMachine
public class StateMachineConfig extends StateMachineConfigurerAdapter<String, String> {

    @Override
    public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
        states
            .withStates()
            .initial("SI") // 初始状态
            .state("S1")  // 状态 S1
            .state("S2");  // 状态 S2
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
        transitions
            .withExternal()
            .source("SI").target("S1").event("E1") // 事件 E1 触发 SI -> S1
            .and()
            .withExternal()
            .source("S1").target("S2").event("E2"); // 事件 E2 触发 S1 -> S2
    }
}

在这个例子中,Spring State Machine 使用状态模式来管理状态和事件驱动的转换。

三、总结

状态模式的优点

  • 消除复杂的条件语句 :状态模式将状态相关的行为分散到各个状态类中,避免了大量的if-elseswitch-case语句,使代码更加清晰。
  • 符合开闭原则:新增状态时,只需添加新的状态类,而无需修改现有代码。修改某个状态的行为时,只需修改对应的状态类,不会影响其他状态。
  • 提高代码的可读性和可维护性:状态和行为被封装在独立的类中,代码结构更加清晰。每个状态类的职责单一,易于理解和测试。
  • 简化上下文逻辑:上下文(Context)只需维护当前状态的引用,并将行为委托给状态对象,逻辑更加简洁。
  • 支持复杂的状态转换:状态模式可以轻松处理多分支的状态转换逻辑(例如,一个状态的下一个状态有多个可能)。

状态模式的缺点

  • 增加类的数量:每个状态都需要一个单独的类,可能会导致类的数量增加,代码结构变得复杂。
  • 状态转换逻辑分散:状态转换逻辑分散在各个状态类中,可能会导致状态转换规则不够直观。
  • 不适合简单的状态管理:如果状态数量很少且状态转换逻辑简单,使用状态模式可能会显得过度设计。

状态模式的使用场景

  • 对象的行为依赖于其状态,且状态数量较多:例如,订单系统(新建、已支付、已发货、已完成等状态)。
  • 状态转换逻辑复杂:例如,游戏角色的状态(站立、跑步、跳跃、攻击等),每个状态的行为和转换条件不同。
  • 需要避免大量的条件语句:例如,线程状态管理(新建、运行、阻塞、终止等),使用状态模式可以避免复杂的条件判断。
  • 状态和行为需要动态切换:例如,TCP 连接状态(建立连接、数据传输、关闭连接等),状态模式可以灵活地管理状态转换。
  • 需要支持分层状态或子状态:例如,工作流引擎中的状态(主状态和子状态),状态模式可以很好地支持分层状态管理。

参考

黑马程序员Java设计模式详解, 23种Java设计模式(图解+框架源码分析+实战)_哔哩哔哩_bilibili

相关推荐
天天进步20158 小时前
UI-TARS 源码解析 #10:parse_action 源码解析:如何用 AST 安全解析模型生成的函数调用?
安全·ui
FuckPatience17 小时前
Telerik UI for WPF 值不能为null。参数名:key
ui·wpf
breeze jiang18 小时前
从原生事件到 React 组件化:用状态驱动模型加载进度 UI
前端·react.js·ui
杨充19 小时前
10.可测试性实战设计
设计模式·开源·代码规范
杨充19 小时前
9.重构十二式的实战
设计模式·开源·代码规范
杨充19 小时前
6.设计原则的全景图
设计模式·开源·全栈
杨充19 小时前
2.面向对象的特性
设计模式
杨充19 小时前
7.SOLID原则案例汇
设计模式·开源·全栈
杨充19 小时前
8.反模式与坏味道
设计模式·开源·代码规范
杨充19 小时前
3.接口vs抽象类比较
设计模式