项目终于用上了 Spring 状态机,太优雅了!

  • 一、状态模式的应用场景
    • 1.1 状态模式在业务场景中的应用
    • 1.2 利用状态机实现订单状态流转控制
  • 二、状态模式中的源码体现
  • 三、状态模式的相关模式
    • 3.1 状态模式与责任链模式
    • 3.2 状态模式与策略模式
  • 四、状态模式的优缺点

状态模式在生活场景中也是比较常见的。比如我们平时网购的订单状态变化,还有平时坐电梯,电梯状态的变化。

图片

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

状态模式(State Pattern)也成为状态机模式(State Machine Pattern),是允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。状态模式中类的行为是由状态决定的,不同的状态下有不同的行为。其意图是让一个对象在其内部改变的时候,其行为也随之改变。状态模式的核心就是状态与行为绑定,不同的状态对应不同的行为。

一、状态模式的应用场景

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

  • 行为随状态改变而改变场景;
  • 一个操作中含有庞大的多分支机构,并且这些分支取决于对象的状态。

状态模式主要包含三种角色:

  • 环境类角色(Context):定义客户端需要的接囗,内部维护一个当前状态实例,并负责具体状态的切换;
  • 抽象状态角色(State):定义该状态下的行为,可以有一个或多个行为;
  • 具体状态角色(ConcreteState):具体实现该状态对应的行为并且在需要的肩况下进行状态切换。

1.1 状态模式在业务场景中的应用

我们在某社区阅读文章的时候,如果觉得某篇文章写得好,就会转发、收藏并且评论。如果用户处于登录情况下,我们就可以做评论、转发、收藏这些行为。否则需要跳转到登录页面,登录之后才能执行先前的动作。那么这里涉及到的状态有两种:已登录和未登录,行为有三种:评论、转发、收藏。下面使用代码来实现这些逻辑,首先创建抽象状态角色类UserState:

csharp 复制代码
public abstractclass UserState {

    private AppContext appContext;

    public void setAppContext(AppContext appContext) {
        this.appContext = appContext;
    }

    public abstract void forward();

    public abstract void collect();

    public abstract void comment(String comment);

}

创建登录状态LoginState类:

typescript 复制代码
public class LoginState extends UserState {

    @Override
    public void forward() {
        System.out.println("转发成功!");
    }

    @Override
    public void collect() {
        System.out.println("收藏成功!");
    }

    @Override
    public void comment(String comment) {
        System.out.println("评论成功,内容是:" + comment);
    }
}

接着创建未登录状态UnLoginState类:

typescript 复制代码
public class UnLoginState extends UserState {

    @Override
    public void forward() {
        forward2Login();
        this.appContext.forward();
    }

    @Override
    public void collect() {
        forward2Login();
        this.appContext.collect();
    }

    @Override
    public void comment(String comment) {
        forward2Login();
        this.appContext.comment(comment);
    }

    private void forward2Login() {
        System.out.println("跳转到登录页面!");
        this.appContext.setState(this.appContext.LOGIN_STATE);
    }
}

创建上下文角色AppContext类:

typescript 复制代码
public class AppContext {

    publicstaticfinal UserState LOGIN_STATE = new LoginState();

    publicstaticfinal UserState UNLOGIN_STATE = new UnLoginState();

    private UserState currentState = UNLOGIN_STATE;

    {
        UNLOGIN_STATE.setAppContext(this);
        LOGIN_STATE.setAppContext(this);
    }

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

    public UserState getState() {
        returnthis.currentState;
    }

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

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

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

}

测试main方法:

typescript 复制代码
public static void main(String[] args) {
    AppContext context = new AppContext();
    context.forward();
    context.collect();
    context.comment("说的太好了,双手双脚给个赞👍");
}

运行结果:

图片

1.2 利用状态机实现订单状态流转控制

状态机是状态模式的一种应用,相当于上下文角色的一个升级版本。在工作流或游戏等各种系统中有大量使用,比如各种工作流引擎,它几乎是状态机的子集和实现,封装状态的变化规则。Spring提供了一个很好的解决方案。Spring的组件名称就叫StateMachine(状态机)。状态机帮助开发者简化状态控制的开发过程,让状态机结构更加层次化。下面来用Spring状态机模拟一个订单状态流转的过程。

1、pom依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-core</artifactId>
    <version>2.0.1.RELEASE</version>
</dependency>

2、创建订单实体类

typescript 复制代码
public class Order {
    privateint 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;
    }
}

3、创建订单状态枚举类和状态转换枚举类

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

4、添加状态流转配置

scss 复制代码
/**
 * 订单状态机配置
 */
@Configuration
@EnableStateMachine(name = "orderStateMachine")
publicclass 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(){
        returnnew 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中的状态,其实并没有进行持久化读取操作
                returnnew DefaultStateMachineContext(order.getStatus(), null, null, null);
            }
        });
    }
}

5、添加订单状态监听器

scss 复制代码
@Component("orderStateListener")
@WithStateMachine(name = "orderStateMachine")
publicclass 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());
        returntrue;
    }

    @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());
        returntrue;
    }

    @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());
        returntrue;
    }
}

6、创建IOrderService接口

scss 复制代码
public interface IOrderService {
    //创建新订单
    Order create();
    //发起支付
    Order pay(int id);
    //订单发货
    Order deliver(int id);
    //订单收货
    Order receive(int id);
    //获取所有订单信息
    Map<Integer, Order> getOrders();
}

7、在Service中添加业务逻辑

scss 复制代码
@Service("orderService")
publicclass OrderServiceImpl implements IOrderService {

    @Autowired
    private StateMachine<OrderStatus, OrderStatusChangeEvent> orderStateMachine;

    @Autowired
    private StateMachinePersister<OrderStatus, OrderStatusChangeEvent, Order> persister;

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

测试main方法:

scss 复制代码
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());

}

二、状态模式中的源码体现

状态模式的具体应用在源码中非常少见,在源码中一般只是提供一种通用的解决方案。如果一定要找,当然也是能找到的。经历千辛万苦,持续烧脑,下面我们来看一个在JSF源码中的Lifecycle类。JSF也算是一个比较经典的前端框架,那么没用过的小伙伴也没关系,我们这是只是分析一下其设计思想。在JSF中它所有页面的处理分为6个阶段,被定义在了Phaseld类中用不同的常量来表示生命周期阶段,源码如下:

ini 复制代码
public class PhaseId implements Comparable {
    privatefinalint ordinal;
    private String phaseName;
    privatestaticint nextOrdinal = 0;
    privatestaticfinal String ANY_PHASE_NAME = "ANY";
    publicstaticfinal PhaseId ANY_PHASE = new PhaseId("ANY");
    privatestaticfinal String RESTORE_VIEW_NAME = "RESTORE_VIEW";
    publicstaticfinal PhaseId RESTORE_VIEW = new PhaseId("RESTORE_VIEW");
    privatestaticfinal String APPLY_REQUEST_VALUES_NAME = "APPLY_REQUEST_VALUES";
    publicstaticfinal PhaseId APPLY_REQUEST_VALUES = new PhaseId("APPLY_REQUEST_VALUES");
    privatestaticfinal String PROCESS_VALIDATIONS_NAME = "PROCESS_VALIDATIONS";
    publicstaticfinal PhaseId PROCESS_VALIDATIONS = new PhaseId("PROCESS_VALIDATIONS");
    privatestaticfinal String UPDATE_MODEL_VALUES_NAME = "UPDATE_MODEL_VALUES";
    publicstaticfinal PhaseId UPDATE_MODEL_VALUES = new PhaseId("UPDATE_MODEL_VALUES");
    privatestaticfinal String INVOKE_APPLICATION_NAME = "INVOKE_APPLICATION";
    publicstaticfinal PhaseId INVOKE_APPLICATION = new PhaseId("INVOKE_APPLICATION");
    privatestaticfinal String RENDER_RESPONSE_NAME = "RENDER_RESPONSE";
    publicstaticfinal PhaseId RENDER_RESPONSE = new PhaseId("RENDER_RESPONSE");
    privatestaticfinal PhaseId[] values;
    publicstaticfinal List VALUES;

    private PhaseId(String newPhaseName) {
        this.ordinal = nextOrdinal++;
        this.phaseName = null;
        this.phaseName = newPhaseName;
    }

    public int compareTo(Object other) {
        returnthis.ordinal - ((PhaseId)other).ordinal;
    }

    public int getOrdinal() {
        returnthis.ordinal;
    }

    public String toString() {
        returnnull == this.phaseName ? String.valueOf(this.ordinal) : this.phaseName + ' ' + this.ordinal;
    }

    static {
        values = new PhaseId[]{ANY_PHASE, RESTORE_VIEW, APPLY_REQUEST_VALUES, PROCESS_VALIDATIONS, UPDATE_MODEL_VALUES, INVOKE_APPLICATION, RENDER_RESPONSE};
        VALUES = Collections.unmodifiableList(Arrays.asList(values));
    }
}

那么这些状态的切换都在Lifecycle的execute()方、去中进行。其中会传一个参数FacesContext对象,最终所有的状态都被FacesContext保存。在此呢,我们就不做继续深入的分析。

三、状态模式的相关模式

3.1 状态模式与责任链模式

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

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

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

3.2 状态模式与策略模式

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

四、状态模式的优缺点

优点:

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

缺点:

  • 类膨胀:如果一个事物具备很多状态,则会造成状态类太多;
  • 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱;
  • 状态模式对开闭原则的支持并不太好,对于可以切换状态的状态模式增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。
相关推荐
qq_3340602123 分钟前
springmvc
java·spring·mvc
迢迢星万里灬1 小时前
Java求职者面试题解析:Spring、Spring Boot、MyBatis框架与源码原理
java·spring boot·spring·mybatis·面试题
虾条_花吹雪3 小时前
5、Spring AI(MCPServer+MCPClient+Ollama)开发环境搭建_第一篇
数据库·人工智能·学习·spring·ai
IT_102412 小时前
springboot从零入门之接口测试!
java·开发语言·spring boot·后端·spring·lua
迢迢星万里灬13 小时前
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术点解析
java·spring boot·spring·mybatis·spring mvc·面试指南
Hanson Huang15 小时前
【Spring AI 1.0.0】Spring AI 1.0.0框架快速入门(2)——Prompt(提示词)
java·人工智能·spring·spring ai
.生产的驴16 小时前
SpringBoot 服务器监控 监控系统开销 获取服务器系统的信息用户信息 运行信息 保持稳定
服务器·spring boot·分布式·后端·spring·spring cloud·信息可视化
没有烦恼的猫猫17 小时前
有关Spring事务的传播机制
spring·事务