状态模式全解析:理论、实现与实际案例深度剖析

状态模式全解析:理论、实现与实际案例深度剖析 目录一、状态模式基本介绍一状态模式介绍二有限状态机二、应用代码举例一状态模式应用举例抽象状态类OrderState具体状态类WaitingForPayState具体状态类PaidState具体状态类ShippedState具体状态类CompletedState具体状态类CancelledState上下文类PDDOrder测试验证二分支逻辑法应用举例订单状态State常量定义分支逻辑法处理OrderStatusMachineForFZ测试验证三查表法应用举例订单状态与事件常量定义查表法处理CoffeeVendingMachine测试验证三、Spring-statemachine状态机框架一基本介绍二应用举例项目中引入相应的依赖应用举例代码验证三使用建议四、实际线上应用案例一业务背景介绍二返奖流程与设计模式实践业务建模模式状态模式工程实践五、总结参考文章链接干货分享感谢您的阅读在复杂的业务开发中你是否遇到过以下场景订单状态繁多且相互依赖代码逻辑随着业务变化变得错综复杂稍有改动便可能引发“多米诺骨牌”式的 Bug这种问题并不罕见但幸运的是状态模式可以成为你的救命稻草。状态模式不仅能清晰地分离状态和行为还能让你的代码具备高扩展性和低耦合性使业务逻辑更加简洁优雅。本篇文章将带你深入了解状态模式的理论基础结合电商订单管理的实际案例手把手教你如何从零实现这一设计模式并探索其变种和实战经验。无论你是刚接触设计模式的初学者还是寻求优化代码结构的资深开发者这篇文章都将带给你启发。准备好了吗让我们开始状态模式的探索之旅吧一、状态模式基本介绍一状态模式介绍状态模式是一种行为型设计模式它允许对象在内部状态发生改变时改变它的行为。该模式将对象的状态封装成不同的类使得对象在不同状态下具有不同的行为从而避免了大量的if-else语句。状态模式包含三个角色上下文(Context)、抽象状态(State)和具体状态(ConcreteState)。上下文(Context)是一个包含状态的对象它可以根据当前状态调用不同的行为。抽象状态(State)定义了一个接口用于封装与上下文(Context)的一个特定状态相关的行为。具体状态(ConcreteState)实现了抽象状态(State)接口并且包含了与该状态相关的行为。状态模式的核心思想是将状态的变化封装到状态类中使得状态的变化对于上下文(Context)对象来说是透明的。当状态发生变化时上下文(Context)对象会自动切换到新的状态并且调用新状态的行为。状态模式的优点包括将状态的变化封装到状态类中使得状态的变化对于上下文(Context)对象来说是透明的。避免了大量的if-else语句使得代码更加简洁和易于维护。可以让状态的变化更加灵活和可扩展。状态模式的缺点包括增加了类的数量使得代码更加复杂。如果状态转换比较复杂可能会导致状态类之间的相互依赖性增加从而影响代码的可维护性。状态模式适用于以下场景当一个对象的行为取决于它的状态并且它必须在运行时根据状态改变它的行为时。当一个对象需要根据它的状态来改变它的行为并且它有很多状态时。当一个对象的状态转换比较复杂时可以使用状态模式来简化状态转换的过程。二有限状态机状态模式与有限状态机的概念紧密相关。状态模式是状态机的一种实现方式。状态机又叫有限状态机Finite State MachineFSM它有3部分组成状态、事件、动作。其中事件也称为转移条件。事件触发状态的转移机动作的执行。不过动作不是必须的也可能只转移状态不执行任何动作。针对状态机的实现方式有分支逻辑法、查表法和状态模式。分支逻辑法使用if-else语句来判断当前状态并根据状态执行相应的操作。这种方式实现简单但是当状态较多时会导致代码复杂度增加不易维护。查表法使用二维数组或哈希表来存储状态转移表根据当前状态和输入条件查找对应的下一个状态。这种方式实现相对简单但是需要事先构建状态转移表当状态较多时表格会变得很大不易维护。状态模式将状态封装成不同的类每个状态类实现自己的行为并且包含了状态转移的逻辑。上下文对象持有当前状态对象当状态发生变化时上下文对象会自动切换到新的状态并且调用新状态的行为。这种方式实现了状态和行为的分离代码结构清晰易于维护和扩展。总的来说分支逻辑法和查表法都是基于条件判断的方式实现状态机适用于状态较少的情况。而状态模式则是一种更加灵活和可扩展的方式适用于状态较多或状态转移较为复杂的情况。二、应用代码举例一状态模式应用举例电商业务场景中订单状态是一个典型的状态机。订单可以处于不同的状态如待支付、已支付、待发货、已发货、已完成等。每个状态都有自己的行为和状态转移规则。下面以订单状态为例介绍状态模式的实现案例。抽象状态类OrderState首先我们定义一个抽象状态类OrderState它包含了订单状态的基本行为和状态转移方法package org.zyf.javabasic.designpatterns.state; /** * author yanfengzhang * description 定义一个抽象状态类OrderState它包含了订单状态的基本行为和状态转移方法 * date 2020/5/24 23:14 */ public abstract class OrderState { protected PDDOrder order; public OrderState(PDDOrder order) { this.order order; } public abstract void pay(); public abstract void cancel(); public abstract void ship(); public abstract void confirm(); }然后我们定义具体状态类如待支付状态、已支付状态、待发货状态、已发货状态和已完成状态。每个具体状态类实现自己的行为和状态转移方法。具体状态类WaitingForPayStatepackage org.zyf.javabasic.designpatterns.state; /** * author yanfengzhang * description 等待支付状态处理 * date 2020/5/24 23:19 */ public class WaitingForPayState extends OrderState{ public WaitingForPayState(PDDOrder order) { super(order); } Override public void pay() { System.out.println(订单已支付); order.setState(new PaidState(order)); } Override public void cancel() { System.out.println(订单已取消); order.setState(new CancelledState(order)); } Override public void ship() { System.out.println(订单未支付不能发货); } Override public void confirm() { System.out.println(订单未支付不能确认收货); } }具体状态类PaidStatepackage org.zyf.javabasic.designpatterns.state; /** * author yanfengzhang * description * date 2020/5/24 23:25 */ public class PaidState extends OrderState{ public PaidState(PDDOrder order) { super(order); } Override public void pay() { System.out.println(订单已支付无需重复支付); } Override public void cancel() { System.out.println(订单已取消); order.setState(new CancelledState(order)); } Override public void ship() { System.out.println(订单已发货); order.setState(new ShippedState(order)); } Override public void confirm() { System.out.println(订单已支付确认收货成功); order.setState(new CompletedState(order)); } }具体状态类ShippedStatepackage org.zyf.javabasic.designpatterns.state; /** * author yanfengzhang * description * date 2020/5/24 23:27 */ public class ShippedState extends OrderState{ public ShippedState(PDDOrder order) { super(order); } Override public void pay() { System.out.println(订单已发货不能支付); } Override public void cancel() { System.out.println(订单已发货不能取消); } Override public void ship() { System.out.println(订单已发货不能重复发货); } Override public void confirm() { System.out.println(订单已发货确认收货成功); order.setState(new CompletedState(order)); } }具体状态类CompletedStatepackage org.zyf.javabasic.designpatterns.state; /** * author yanfengzhang * description * date 2020/5/24 23:28 */ public class CompletedState extends OrderState{ public CompletedState(PDDOrder order) { super(order); } Override public void pay() { System.out.println(订单已完成不能支付); } Override public void cancel() { System.out.println(订单已完成不能取消); } Override public void ship() { System.out.println(订单已完成不能发货); } Override public void confirm() { System.out.println(订单已完成不能重复确认收货); } }具体状态类CancelledStatepackage org.zyf.javabasic.designpatterns.state; /** * author yanfengzhang * description * date 2020/5/24 23:29 */ public class CancelledState extends OrderState { public CancelledState(PDDOrder order) { super(order); } Override public void pay() { System.out.println(订单已取消不能支付); } Override public void cancel() { System.out.println(订单已取消不能重复取消); } Override public void ship() { System.out.println(订单已取消不能发货); } Override public void confirm() { System.out.println(订单已取消不能确认收货); } }上下文类PDDOrder定义上下文类PDDOrder它持有当前订单状态对象并且提供了一些操作方法package org.zyf.javabasic.designpatterns.state; /** * author yanfengzhang * description 定义上下文类 * date 2020/5/24 23:17 */ public class PDDOrder { private OrderState state; public PDDOrder() { state new WaitingForPayState(this); } public void setState(OrderState state) { this.state state; } public void pay() { state.pay(); } public void cancel() { state.cancel(); } public void ship() { state.ship(); } public void confirm() { state.confirm(); } }测试验证测试代码如下package org.zyf.javabasic.designpatterns.state; /** * author yanfengzhang * description * date 2020/5/24 23:35 */ public class OrderStateTest { public static void main(String[] args) { PDDOrder order new PDDOrder(); // 初始状态为WaitingForPayState order.ship(); // 输出订单未支付不能发货 order.confirm(); // 输出订单未支付不能确认收货 order.cancel(); // 输出订单已取消 order.pay(); // 输出订单已支付 order.pay(); // 输出订单已支付无需重复支付 order.cancel(); // 输出订单已取消 order.pay(); // 输出订单已支付 order.ship(); // 输出订单已发货 order.confirm(); // 输出订单已支付确认收货成功 order.confirm(); // 输出订单已完成不能重复确认收货 order.ship(); // 输出订单已完成不能发货 order.cancel(); // 输出订单已完成不能取消 } }验证结果展示订单未支付不能发货 订单未支付不能确认收货 订单已取消 订单已取消不能支付 订单已取消不能支付 订单已取消不能重复取消 订单已取消不能支付 订单已取消不能发货 订单已取消不能确认收货 订单已取消不能确认收货 订单已取消不能发货 订单已取消不能重复取消二分支逻辑法应用举例分支逻辑法使用if-else语句来判断当前状态并根据状态执行相应的操作。这种方式实现简单但是当状态较多时会导致代码复杂度增加不易维护。以下是使用分支逻辑法实现的电商订单状态机代码。订单状态State常量定义package org.zyf.javabasic.designpatterns.state.statemachine; /** * author yanfengzhang * description 订单相关常量处理 * date 2020/5/24 23:46 */ public class OrderCons { public static final class State { public static final int WAITING_FOR_PAY 1; public static final int PAID 2; public static final int SHIPPED 3; public static final int CONFIRMED 4; public static final int CANCELED 5; } }分支逻辑法处理OrderStatusMachineForFZpackage org.zyf.javabasic.designpatterns.state.statemachine; /** * author yanfengzhang * description 使用分支逻辑法实现的电商订单状态机代码 * date 2020/5/24 23:19 */ public class OrderStatusMachineForFZ { private int state; public OrderStatusMachineForFZ() { this.state OrderCons.State.WAITING_FOR_PAY; } public void pay() { if (state OrderCons.State.WAITING_FOR_PAY) { System.out.println(订单已支付); state OrderCons.State.PAID; } else if (state OrderCons.State.PAID) { System.out.println(订单已支付无需重复支付); } else if (state OrderCons.State.SHIPPED) { System.out.println(订单已发货不能支付); } else if (state OrderCons.State.CONFIRMED) { System.out.println(订单已完成不能支付); } else if (state OrderCons.State.CANCELED) { System.out.println(订单已取消不能支付); } } public void ship() { if (state OrderCons.State.WAITING_FOR_PAY) { System.out.println(订单未支付不能发货); } else if (state OrderCons.State.PAID) { System.out.println(订单已发货); state OrderCons.State.SHIPPED; } else if (state OrderCons.State.SHIPPED) { System.out.println(订单已发货不能重复发货); } else if (state OrderCons.State.CONFIRMED) { System.out.println(订单已完成不能发货); } else if (state OrderCons.State.CANCELED) { System.out.println(订单已取消不能发货); } } public void confirm() { if (state OrderCons.State.WAITING_FOR_PAY) { System.out.println(订单未支付不能确认收货); } else if (state OrderCons.State.PAID) { System.out.println(订单已支付不能确认收货); } else if (state OrderCons.State.SHIPPED) { System.out.println(订单已支付确认收货成功); state OrderCons.State.CONFIRMED; } else if (state OrderCons.State.CONFIRMED) { System.out.println(订单已完成不能重复确认收货); } else if (state OrderCons.State.CANCELED) { System.out.println(订单已取消不能确认收货); } } public void cancel() { if (state OrderCons.State.WAITING_FOR_PAY) { System.out.println(订单已取消); state OrderCons.State.CANCELED; } else if (state OrderCons.State.PAID) { System.out.println(订单已支付不能取消); } else if (state OrderCons.State.SHIPPED) { System.out.println(订单已发货不能取消); } else if (state OrderCons.State.CONFIRMED) { System.out.println(订单已完成不能取消); } else if (state OrderCons.State.CANCELED) { System.out.println(订单已取消不能重复取消); } } }测试验证测试代码如下package org.zyf.javabasic.designpatterns.state.statemachine; /** * author yanfengzhang * description * date 2020/5/24 23:22 */ public class OrderStatusMachineForFZTest { public static void main(String[] args) { OrderStatusMachineForFZ order new OrderStatusMachineForFZ(); // 初始状态为WAITING_FOR_PAY order.ship(); // 输出订单未支付不能发货 order.confirm(); // 输出订单未支付不能确认收货 order.cancel(); // 输出订单已取消 order.pay(); // 输出订单已支付 order.pay(); // 输出订单已支付无需重复支付 order.cancel(); // 输出订单已取消 order.pay(); // 输出订单已支付 order.ship(); // 输出订单已发货 order.confirm(); // 输出订单已支付确认收货成功 order.confirm(); // 输出订单已完成不能重复确认收货 order.ship(); // 输出订单已完成不能发货 order.cancel(); // 输出订单已完成不能取消 } }验证结果展示订单未支付不能发货 订单未支付不能确认收货 订单已取消 订单已取消不能支付 订单已取消不能支付 订单已取消不能重复取消 订单已取消不能支付 订单已取消不能发货 订单已取消不能确认收货 订单已取消不能确认收货 订单已取消不能发货 订单已取消不能重复取消三查表法应用举例查表法使用二维数组或哈希表来存储状态转移表根据当前状态和输入条件查找对应的下一个状态。这种方式实现相对简单但是需要事先构建状态转移表当状态较多时表格会变得很大不易维护。以下是一个简单的咖啡自动售货机状态机的示例代码。订单状态与事件常量定义private static class State { private static final int IDLE 0; private static final int MAKING_COFFEE 1; private static final int DISPENSING_COFFEE 2; } private static class Event { private static final int INSERT_COIN 0; private static final int MAKE_COFFEE 1; private static final int DISPENSE_COFFEE 2; }查表法处理CoffeeVendingMachinepackage org.zyf.javabasic.designpatterns.state.statemachine; /** * author yanfengzhang * description * date 2020/5/24 23:58 */ public class CoffeeVendingMachine { private int[][] transitionTable { // IDLE {State.MAKING_COFFEE, State.IDLE}, // MAKING_COFFEE {State.DISPENSING_COFFEE, State.MAKING_COFFEE}, // DISPENSING_COFFEE {State.IDLE, State.IDLE} }; private int state; public CoffeeVendingMachine() { this.state State.IDLE; } public void insertCoin() { if (state State.IDLE) { state transitionTable[state][Event.INSERT_COIN]; System.out.println(请按下咖啡按钮); } else { System.out.println(当前状态无法插入硬币); } } public void makeCoffee() { if (state State.MAKING_COFFEE) { state transitionTable[state][Event.MAKE_COFFEE]; System.out.println(正在制作咖啡请稍等); } else { System.out.println(当前状态无法制作咖啡); } } public void dispenseCoffee() { if (state State.DISPENSING_COFFEE) { state transitionTable[state][Event.DISPENSE_COFFEE]; System.out.println(请取走您的咖啡); } else { System.out.println(当前状态无法取走咖啡); } } private static class State { private static final int IDLE 0; private static final int MAKING_COFFEE 1; private static final int DISPENSING_COFFEE 2; } private static class Event { private static final int INSERT_COIN 0; private static final int MAKE_COFFEE 1; private static final int DISPENSE_COFFEE 2; } }我们定义了一个咖啡自动售货机状态机它有3个状态IDLE空闲、MAKING_COFFEE制作咖啡和DISPENSING_COFFEE取走咖啡。它有3个事件INSERT_COIN插入硬币、MAKE_COFFEE制作咖啡和DISPENSE_COFFEE取走咖啡。我们可以使用这个状态机来管理咖啡自动售货机的状态。例如当一个顾客插入硬币时我们可以调用insertCoin()方法来将咖啡自动售货机的状态从IDLE转换为MAKING_COFFEE。当咖啡自动售货机正在制作咖啡时我们可以调用makeCoffee()方法来将咖啡自动售货机的状态从MAKING_COFFEE转换为DISPENSING_COFFEE。当咖啡自动售货机完成制作并准备好将咖啡分配给顾客时我们可以调用dispenseCoffee()方法来将咖啡自动售货机的状态从DISPENSING_COFFEE转换为IDLE。使用状态机可以帮助我们更好地管理咖啡自动售货机的状态避免了一些状态转换的错误。测试验证测试代码如下package org.zyf.javabasic.designpatterns.state.statemachine; /** * author yanfengzhang * description * date 2020/5/24 23:59 */ public class CoffeeVendingMachineTest { public static void main(String[] args) { CoffeeVendingMachine vendingMachine new CoffeeVendingMachine(); vendingMachine.insertCoin(); vendingMachine.makeCoffee(); vendingMachine.dispenseCoffee(); } }验证结果展示请按下咖啡按钮 正在制作咖啡请稍等 当前状态无法取走咖啡三、Spring-statemachine状态机框架一基本介绍spring-statemachine是一个基于Spring框架的状态机框架它提供了一种方便的方式来实现状态机并且可以与Spring框架无缝集成。使用spring-statemachine可以帮助我们更方便地实现状态机并且可以提高代码的可读性和可维护性。spring-statemachine提供了以下功能状态机的定义和管理可以通过定义状态、事件、转换和动作等来管理状态机。状态机的启动和停止可以通过启动和停止状态机来控制状态机的运行。状态机的状态转换可以通过发送事件来触发状态机的状态转换。状态机的监听器可以通过监听器来监控状态机的状态变化和事件处理过程。状态机的条件判断可以通过Guard来对事件进行条件判断避免不符合条件的事件被处理。状态机的动作处理可以通过动作来处理状态机的状态转换过程中的业务逻辑。状态机的持久化可以通过扩展依赖来实现状态机的持久化如使用JPA存储状态机。总之spring-statemachine提供了一种方便的方式来实现状态机并且可以与Spring框架无缝集成。使用spring-statemachine可以帮助我们更方便地实现状态机并且可以提高代码的可读性和可维护性。二应用举例项目中引入相应的依赖dependency groupIdorg.springframework.statemachine/groupId artifactIdspring-statemachine-core/artifactId version3.2.0/version exclusions exclusion groupIdcom.vaadin.external.google/groupId artifactIdandroid-json/artifactId /exclusion /exclusions /dependency dependency groupIdio.projectreactor/groupId artifactIdreactor-core/artifactId version3.4.0/version /dependency应用举例spring-statemachine提供了一些接口和类可以帮助我们定义状态、事件、转换和动作并且可以自动管理状态机的状态。以下是一个简单的示例首先我们需要定义状态、事件和转换package org.zyf.javabasic.designpatterns.state.statemachine.spring; /** * author yanfengzhang * description * date 2020/5/26 23:13 */ public enum States { IDLE, MAKING_COFFEE, DISPENSING_COFFEE } package org.zyf.javabasic.designpatterns.state.statemachine.spring; /** * author yanfengzhang * description * date 2020/5/26 23:14 */ public enum Events { INSERT_COIN, MAKE_COFFEE, DISPENSE_COFFEE }在这个示例中我们定义了3个状态IDLE、MAKING_COFFEE和DISPENSING_COFFEE以及3个事件INSERT_COIN、MAKE_COFFEE和DISPENSE_COFFEE。接着使用Configuration和EnableStateMachine注解来告诉Spring框架这是一个状态机配置类并且启用状态机。package org.zyf.javabasic.designpatterns.state.statemachine.spring; import org.springframework.context.annotation.Configuration; import org.springframework.statemachine.config.EnableStateMachine; import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; import java.util.EnumSet; /** * author yanfengzhang * description 状态机配置类 * date 2020/5/26 23:15 */ Configuration EnableStateMachine public class CoffeeStateMachineConfig extends EnumStateMachineConfigurerAdapterStates, Events { Override public void configure(StateMachineStateConfigurerStates, Events states) throws Exception { states .withStates() .initial(States.IDLE) .states(EnumSet.allOf(States.class)); } Override public void configure(StateMachineTransitionConfigurerStates, Events transitions) throws Exception { transitions .withExternal() .source(States.IDLE).target(States.MAKING_COFFEE) .event(Events.INSERT_COIN) .and() .withExternal() .source(States.MAKING_COFFEE).target(States.DISPENSING_COFFEE) .event(Events.MAKE_COFFEE) .and() .withExternal() .source(States.DISPENSING_COFFEE).target(States.IDLE) .event(Events.DISPENSE_COFFEE); } }然后我们可以定义状态机的动作package org.zyf.javabasic.designpatterns.state.statemachine.spring; import org.springframework.messaging.Message; import org.springframework.statemachine.listener.StateMachineListenerAdapter; import org.springframework.statemachine.state.State; /** * author yanfengzhang * description 定义状态机的动作 * 定义了一个状态机监听器它可以在状态机状态发生变化或者事件不被接受时输出一些信息 * date 2020/5/26 23:17 */ public class CoffeeStateMachineListener extends StateMachineListenerAdapterStates, Events { Override public void stateChanged(StateStates, Events from, StateStates, Events to) { System.out.println(状态从 from 变为 to); } Override public void eventNotAccepted(MessageEvents event) { System.out.println(事件 event.getPayload() 不被接受); } }在这个示例中我们定义了一个状态机监听器它可以在状态机状态发生变化或者事件不被接受时输出一些信息。代码验证测试代码package org.zyf.javabasic.designpatterns.state.statemachine.spring; import lombok.extern.slf4j.Slf4j; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.statemachine.StateMachine; import org.springframework.test.context.junit4.SpringRunner; import org.zyf.javabasic.ZYFApplication; /** * author yanfengzhang * description * date 2020/5/26 23:22 */ RunWith(SpringRunner.class) SpringBootTest(classes ZYFApplication.class, webEnvironment SpringBootTest.WebEnvironment.RANDOM_PORT) Slf4j public class CoffeeStateMachineApplicationTest { Autowired private StateMachineStates, Events stateMachine; Test public void testStateMachine() { stateMachine.start(); System.out.println(stateMachine.getState().getId()); stateMachine.sendEvent(Events.INSERT_COIN); System.out.println(stateMachine.getState().getId()); stateMachine.sendEvent(Events.MAKE_COFFEE); System.out.println(stateMachine.getState().getId()); stateMachine.sendEvent(Events.DISPENSE_COFFEE); System.out.println(stateMachine.getState().getId()); } }验证结果IDLE MAKING_COFFEE DISPENSING_COFFEE IDLE三使用建议在使用spring-statemachine开发过程中有以下几点建议状态机的设计应该尽可能简单和清晰避免出现复杂的状态转换逻辑这样可以降低代码的复杂度和维护成本。将状态机的状态、事件、转换和动作等定义在一个单独的类中这样可以方便地进行管理和修改。使用状态机监听器来监控状态机的状态变化和事件处理过程这样可以方便地调试和排查问题。在状态机的转换过程中可以使用Guard来对事件进行条件判断避免不符合条件的事件被处理。在状态机的动作中应该尽可能避免进行复杂的业务逻辑处理可以将业务逻辑处理放在状态机之外这样可以提高状态机的可重用性和可测试性。在使用spring-statemachine时应该注意版本的兼容性避免出现不兼容的情况。总之使用spring-statemachine可以帮助我们更方便地实现状态机并且可以与Spring框架无缝集成。在开发过程中我们应该尽可能简化状态机的设计使用监听器来监控状态机的状态变化和事件处理过程避免在状态机中进行复杂的业务逻辑处理。四、实际线上应用案例一业务背景介绍“邀请下单”是美团外卖用户邀请其他用户下单后给予奖励的平台。即用户A邀请用户B并且用户B在美团下单后给予用户A一定的现金奖励以下简称返奖。同时为了协调成本与收益的关系返奖会有多个计算策略。邀请下单后台主要涉及两个技术要点返奖金额的计算涉及到不同的计算规则。从邀请开始到返奖结束的整个流程。后续重点分析返奖流程与设计模式实践二返奖流程与设计模式实践业务建模当受邀人在接受邀请人的邀请并且下单后返奖后台接收到受邀人的下单记录此时邀请人也进入返奖流程。首先我们订阅用户订单消息并对订单进行返奖规则校验。例如是否使用红包下单是否在红包有效期内下单订单是否满足一定的优惠金额等等条件。当满足这些条件以后我们将订单信息放入延迟队列中进行后续处理。经过TN天之后处理该延迟消息判断用户是否对该订单进行了退款如果未退款对用户进行返奖。若返奖失败后台还有返奖补偿流程再次进行返奖。其流程如下图所示我们对上述业务流程进行领域建模在接收到订单消息后用户进入待校验状态在校验后若校验通过用户进入预返奖状态并放入延迟队列。若校验未通过用户进入不返奖状态结束流程TN天后处理延迟消息若用户未退款进入待返奖状态。若用户退款进入失败状态结束流程执行返奖若返奖成功进入完成状态结束流程。若返奖不成功进入待补偿状态待补偿状态的用户会由任务定期触发补偿机制直至返奖成功进入完成状态保障流程结束。可以看到我们通过建模将返奖流程的多个步骤映射为系统的状态。对于系统状态的表述DDD中常用到的概念是领域事件另外也提及过事件溯源的实践方案。当然在设计模式中也有一种能够表述系统状态的代码模型那就是状态模式。在邀请下单系统中我们的主要流程是返奖。对于返奖每一个状态要进行的动作和操作都是不同的。因此使用状态模式能够帮助我们对系统状态以及状态间的流转进行统一的管理和扩展。模式状态模式模式定义当一个对象内在状态改变时允许其改变行为这个对象看起来像改变了其类。状态模式的通用类图如下图所示对比策略模式的类型会发现和状态模式的类图很类似但实际上有很大的区别具体体现在concrete class上。策略模式通过Context产生唯一一个ConcreteStrategy作用于代码中而状态模式则是通过context组织多个ConcreteState形成一个状态转换图来实现业务逻辑。接下来我们通过一段通用代码来解释怎么使用状态模式//定义一个抽象的状态类 public abstract class State { Context context; public void setContext(Context context) { this.context context; } public abstract void handle1(); public abstract void handle2(); } //定义状态A public class ConcreteStateA extends State { Override public void handle1() {} //本状态下必须要处理的事情 ​ Override public void handle2() { super.context.setCurrentState(Context.contreteStateB); //切换到状态B super.context.handle2(); //执行状态B的任务 } } //定义状态B public class ConcreteStateB extends State { Override public void handle2() {} //本状态下必须要处理的事情... Override public void handle1() { super.context.setCurrentState(Context.contreteStateA); //切换到状态A super.context.handle1(); //执行状态A的任务 } } //定义一个上下文管理环境 public class Context { public final static ConcreteStateA contreteStateA new ConcreteStateA(); public final static ConcreteStateB contreteStateB new ConcreteStateB(); ​ private State CurrentState; public State getCurrentState() {return CurrentState;} ​ public void setCurrentState(State currentState) { this.CurrentState currentState; this.CurrentState.setContext(this); } ​ public void handle1() {this.CurrentState.handle1();} public void handle2() {this.CurrentState.handle2();} } //定义client执行 public class client { public static void main(String[] args) { Context context new Context(); context.setCurrentState(new ContreteStateA()); context.handle1(); context.handle2(); } }工程实践通过前文对状态模式的简介我们可以看到当状态之间的转换在不是非常复杂的情况下通用的状态模式存在大量的与状态无关的动作从而产生大量的无用代码。在我们的实践中一个状态的下游不会涉及特别多的状态装换所以我们简化了状态模式。当前的状态只负责当前状态要处理的事情状态的流转则由第三方类负责。其实践代码如下//返奖状态执行的上下文 public class RewardStateContext { ​ private RewardState rewardState; public void setRewardState(RewardState currentState) {this.rewardState currentState;} public RewardState getRewardState() {return rewardState;} public void echo(RewardStateContext context, Request request) { rewardState.doReward(context, request); } } ​ public abstract class RewardState { abstract void doReward(RewardStateContext context, Request request); } ​ //待校验状态 public class OrderCheckState extends RewardState { Override public void doReward(RewardStateContext context, Request request) { orderCheck(context, request); //对进来的订单进行校验判断是否用券是否满足优惠条件等等 } } ​ //待补偿状态 public class CompensateRewardState extends RewardState { Override public void doReward(RewardStateContext context, Request request) { compensateReward(context, request); //返奖失败需要对用户进行返奖补偿 } } ​ //预返奖状态待返奖状态成功状态失败状态(此处逻辑省略) //.. ​ public class InviteRewardServiceImpl { public boolean sendRewardForInvtee(long userId, long orderId) { Request request new Request(userId, orderId); RewardStateContext rewardContext new RewardStateContext(); rewardContext.setRewardState(new OrderCheckState()); rewardContext.echo(rewardContext, request); //开始返奖订单校验 //此处的if-else逻辑只是为了表达状态的转换过程并非实际的业务逻辑 if (rewardContext.isResultFlag()) { //如果订单校验成功进入预返奖状态 rewardContext.setRewardState(new BeforeRewardCheckState()); rewardContext.echo(rewardContext, request); } else {//如果订单校验失败进入返奖失败流程... rewardContext.setRewardState(new RewardFailedState()); rewardContext.echo(rewardContext, request); return false; } if (rewardContext.isResultFlag()) {//预返奖检查成功进入待返奖流程... rewardContext.setRewardState(new SendRewardState()); rewardContext.echo(rewardContext, request); } else { //如果预返奖检查失败进入返奖失败流程... rewardContext.setRewardState(new RewardFailedState()); rewardContext.echo(rewardContext, request); return false; } if (rewardContext.isResultFlag()) { //返奖成功进入返奖结束流程... rewardContext.setRewardState(new RewardSuccessState()); rewardContext.echo(rewardContext, request); } else { //返奖失败进入返奖补偿阶段... rewardContext.setRewardState(new CompensateRewardState()); rewardContext.echo(rewardContext, request); } if (rewardContext.isResultFlag()) { //补偿成功进入返奖完成阶段... rewardContext.setRewardState(new RewardSuccessState()); rewardContext.echo(rewardContext, request); } else { //补偿失败仍然停留在当前态直至补偿成功或多次补偿失败后人工介入处理 rewardContext.setRewardState(new CompensateRewardState()); rewardContext.echo(rewardContext, request); } return true; } }状态模式的核心是封装将状态以及状态转换逻辑封装到类的内部来实现也很好的体现了“开闭原则”和“单一职责原则”。每一个状态都是一个子类不管是修改还是增加状态只需要修改或者增加一个子类即可。在我们的应用场景中状态数量以及状态转换远比上述例子复杂通过“状态模式”避免了大量的if-else代码让我们的逻辑变得更加清晰。同时由于状态模式的良好的封装性以及遵循的设计原则让我们在复杂的业务场景中能够游刃有余地管理各个状态。五、总结状态模式是一种优雅而强大的设计模式特别适用于复杂的状态切换场景。通过将状态与行为分离它有效降低了系统的复杂性和耦合度使代码更具扩展性和可维护性。在实际应用中状态模式不仅能够帮助我们清晰地组织代码逻辑还能显著减少后续需求变更带来的维护成本。在本文中我们以电商订单管理为例详细讲解了状态模式的核心思想及实现步骤并探讨了其变种管道模式的灵活应用。可以看到设计模式的真正价值并不在于“死板的理论”而在于如何结合实际业务场景发挥它们的威力。最后设计模式的应用需要经验的积累与灵活的思维。面对不同的业务需求我们应该在理解模式本质的基础上选择最适合的模式并进行适当调整避免过度设计或强行套用。希望本篇文章能为你提供新的思路让你在面对复杂的开发问题时更加游刃有余。未来在代码中实现优雅的逻辑不妨多想想“状态模式”能否成为你的利器参考文章链接状态设计模式状态模式(State Pattern) - 简书设计模式在外卖营销业务中的实践 - 美团技术团队