Java状态模式源码剖析及使用场景

状态模式

一、介绍

状态模式(State Pattern)是一种行为型设计模式,它允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。

状态模式的核心思想是将对象的状态封装成独立的类,使得对象可以根据自身的状态来获取不同的行为。当对象的内部状态发生改变时,其行为也会随之改变。

状态模式的主要角色有:

  1. Context(上下文): 定义客户感兴趣的接口,并维护一个具体状态类的实例。

  2. State(状态): 这是一个抽象状态类,它定义了一个与Context的一个或多个行为相关的接口。

  3. ConcreteState(具体状态): 实现了抽象状态类所对应的行为。每一个子类实现对应于Context的一种状态。

状态模式的优点:

  1. 解耦了状态和行为,将它们分布到不同的类中,使得状态的改变不会影响行为的改变,行为的改变也不会影响状态的改变。
  2. 每种状态都可以根据自身的特点执行业务逻辑,因为每种状态对应一个子类。
  3. 支持对象状态的切换。
  4. 方便进行扩展,由于状态和行为是分开的,所以可以很方便地添加新的状态和行为。

状态模式的缺点:

  1. 会导致系统中类的个数增多。
  2. 在使用的过程中,需要分清系统中的每一种状态所对应的行为,以免产生错误。

状态模式的应用场景:

  1. 行为随状态改变而改变的场景。
  2. 一个操作中含有大量分支语句时,可以使用状态模式来把相关的分支语句转移到各个状态中。
  3. 处理订单流程、用户状态管理等领域都可以使用状态模式。

二、订单处理系统中使用

需求:一个简单的订单处理系统,订单从下单开始,经过多个状态的转换,最终到达完成状态。订单可能存在的状态包括:已下单(OrderedState)、已付款(PaidState)、已发货(ShippedState)、已签收(ReceivedState)、已完成(CompletedState)。当订单处于不同状态时,对应的操作也不同,比如在已下单状态下可以支付订单,在已付款状态下可以发货,以此类推。

java 复制代码
// 订单状态接口,定义订单可执行的操作,如支付、发货、签收、完成等
interface OrderState {
    void pay();
    void ship();
    void receive();
    void complete();
}

// 已下单状态,实现了订单OrderState在不同状态下的具体行为。
class OrderedState implements OrderState {
    private Order order;

    public OrderedState(Order order) {
        this.order = order;
    }

    @Override
    public void pay() {
        System.out.println("已支付订单 " + order.getOrderId());
        order.setState(new PaidState(order));
    }

    @Override
    public void ship() {
        System.out.println("订单尚未支付,无法发货");
    }

    @Override
    public void receive() {
        System.out.println("订单尚未发货,无法签收");
    }

    @Override
    public void complete() {
        System.out.println("订单尚未发货,无法完成");
    }
}

// 已付款状态
class PaidState implements OrderState {
    private Order order;

    public PaidState(Order order) {
        this.order = order;
    }

    @Override
    public void pay() {
        System.out.println("订单已经支付,无需重复支付");
    }

    @Override
    public void ship() {
        System.out.println("正在发货订单 " + order.getOrderId());
        order.setState(new ShippedState(order));
    }

    @Override
    public void receive() {
        System.out.println("订单尚未发货,无法签收");
    }

    @Override
    public void complete() {
        System.out.println("订单尚未发货,无法完成");
    }
}

// 其他状态类实现...

// 订单上下文,它维护了订单的当前状态,并提供了相应的操作方法。这些方法内部会委托给当前状态对象执行具体的操作。
class Order {
    private String orderId;
    private OrderState state;

    public Order(String orderId) {
        this.orderId = orderId;
        this.state = new OrderedState(this); // 初始状态为已下单
    }

    public void pay() {
        state.pay();
    }

    public void ship() {
        state.ship();
    }

    public void receive() {
        state.receive();
    }

    public void complete() {
        state.complete();
    }

    public String getOrderId() {
        return orderId;
    }
	
	//改变订单的当前状态
    public void setState(OrderState state) {
        this.state = state;
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        Order order = new Order("1001");

        order.pay(); // 已支付订单 1001
        order.ship(); // 正在发货订单 1001
        order.receive(); // 已签收订单 1001
        order.complete(); // 订单已完成
    }
}
  1. OrderState 接口定义了订单可执行的操作,如支付、发货、签收、完成等。
  2. OrderedStatePaidStateShippedStateReceivedStateCompletedState 分别实现了订单在不同状态下的具体行为。
  3. Order 类是状态模式的上下文,它维护了订单的当前状态,并提供了相应的操作方法,如 pay()ship() 等。这些方法内部会委托给当前状态对象执行具体的操作。
  4. Order 类的 setState() 方法用于改变订单的当前状态。
  5. 在客户端代码中,我们创建了一个订单对象,然后依次调用支付、发货、签收、完成操作,订单状态会随着操作的执行而发生改变。

通过状态模式的实现,将订单的不同状态和行为分离开来,使得订单在状态改变时可以自动切换行为,提高了代码的可扩展性和可维护性。同时,由于每个状态对应一个子类,我们也可以很方便地添加新的状态和行为,满足未来需求的变化。

三、Spring 中事务怎么使用状态模式?

在 Spring 事务管理中,TransactionDefinition 接口定义了事务的各种属性,比如传播行为、隔离级别、超时时间等。而 TransactionStatus 接口则表示事务的不同状态,它是一个标记接口,具体的状态由其实现类 DefaultTransactionStatus 来维护。

DefaultTransactionStatus 内部使用一个 State 枚举来表示事务的不同状态,包括以下几种:

java 复制代码
private enum State {
    ACTIVE,
    COMMITTED,
    ROLLED_BACK,
    COMMITTED_AFTER_ROLLBACK,
    ROLLBACK_AFTER_COMMITTED
}
  • ACTIVE: 事务处于活动状态
  • COMMITTED: 事务已提交
  • ROLLED_BACK: 事务已回滚
  • COMMITTED_AFTER_ROLLBACK: 事务在回滚后又被提交
  • ROLLBACK_AFTER_COMMITTED: 事务在提交后又被回滚

DefaultTransactionStatus 实现了 TransactionStatus 接口中的方法,根据当前的状态执行相应的操作。例如 setRollbackOnly() 方法用于标记事务需要回滚

java 复制代码
public void setRollbackOnly() {
    switch (this.state) {
        case ACTIVE:
            setState(State.ROLLED_BACK);
            break;
        case COMMITTED:
            setState(State.ROLLBACK_AFTER_COMMITTED);
            break;
        case ROLLED_BACK:
            // Do nothing
            break;
        case COMMITTED_AFTER_ROLLBACK:
            setState(State.ROLLBACK_AFTER_COMMITTED);
            break;
        case ROLLBACK_AFTER_COMMITTED:
            // Do nothing
            break;
        default:
            throw new IllegalStateException("Invalid state transition attempted");
    }
}

这段代码根据当前事务的状态执行不同的操作。如果事务处于 ACTIVE 状态,则将其标记为 ROLLED_BACK。如果事务已经提交,则将其标记为 ROLLBACK_AFTER_COMMITTED 等。

另外,Spring 还提供了 TransactionSynchronization 接口,用于在事务生命周期的不同阶段执行特定的逻辑。AbstractPlatformTransactionManager 类中的 invokeAfterCommitinvokeAfterRollback 方法就会根据事务的当前状态决定是否调用相应的同步回调方法

java 复制代码
protected void invokeAfterCommit(DefaultTransactionStatus status) {
    if (status.getState() == DefaultTransactionStatus.State.COMMITTED_AFTER_ROLLBACK) {
        invokeAfterRollbackHandlers(status);
    }
    invokeAfterCommitHandlers(status);
}

protected void invokeAfterRollback(DefaultTransactionStatus status) {
    if (status.getState() == DefaultTransactionStatus.State.ROLLBACK_AFTER_COMMITTED) {
        invokeAfterCommitHandlers(status);
    }
    invokeAfterRollbackHandlers(status);
}

这些方法会根据事务的状态决定是先执行 commit 回调还是 rollback 回调,或者两者都执行。

看到 Spring 在处理事务管理时,使用了状态模式来封装事务的不同状态,并根据状态的变化执行相应的操作。这种设计使得事务管理的代码更加清晰、灵活和可扩展。如果需要添加新的状态或行为,只需要修改 State 枚举和相关方法,而不会影响整个事务管理框架的其他部分。

相关推荐
天呐草莓13 小时前
企业微信运维手册
java·运维·网络·python·微信小程序·企业微信·微信开放平台
小兔崽子去哪了13 小时前
Java 登录专题
java·spring boot·后端
毕设源码-邱学长13 小时前
【开题答辩全过程】以 高校跨校选课系统为例,包含答辩的问题和答案
java·eclipse
@淡 定13 小时前
缓存原理详解
java·spring·缓存
带刺的坐椅13 小时前
超越 SpringBoot 4.0了吗?OpenSolon v3.8, v3.7.4, v3.6.7 发布
java·ai·springboot·web·solon·flow·mcp
就叫飞六吧13 小时前
基于spring web实现简单分片上传demo
java·前端·spring
apihz13 小时前
免费手机号归属地查询API接口详细教程
android·java·运维·服务器·开发语言
程序员小假13 小时前
学院本大二混子终于找到实习了...
java·后端
回吐泡泡oO13 小时前
找不到rar.RarArchiveInputStream?JAVA解压RAR5的方案。
java·开发语言
jiayong2313 小时前
AI应用领域编程语言选择指南:Java vs Python vs Go
java·人工智能·python