首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

快速应用设计模式:状态模式使用

  • 25-03-02 12:01
  • 2307
  • 9965
blog.csdn.net

目录

一、状态模式基本介绍

(一)状态模式介绍

(二)有限状态机

二、应用代码举例

(一)状态模式应用举例

抽象状态类OrderState

具体状态类WaitingForPayState

具体状态类PaidState

具体状态类ShippedState

具体状态类CompletedState

具体状态类CancelledState

上下文类PDDOrder

测试验证

(二)分支逻辑法应用举例

订单状态State常量定义

分支逻辑法处理OrderStatusMachineForFZ

测试验证

(三)查表法应用举例

订单状态与事件常量定义

查表法处理CoffeeVendingMachine

测试验证

三、Spring-statemachine状态机框架

(一)基本介绍

(二)应用举例

项目中引入相应的依赖

应用举例

代码验证

(三)使用建议

四、实际线上应用案例

(一)业务背景介绍

(二)返奖流程与设计模式实践

业务建模

模式:状态模式

工程实践

参考文章链接


干货分享,感谢您的阅读!

一、状态模式基本介绍

(一)状态模式介绍

状态模式是一种行为型设计模式,它允许对象在内部状态发生改变时改变它的行为。该模式将对象的状态封装成不同的类,使得对象在不同状态下具有不同的行为,从而避免了大量的if-else语句。

状态模式包含三个角色:上下文(Context)、抽象状态(State)和具体状态(ConcreteState)。

  • 上下文(Context)是一个包含状态的对象,它可以根据当前状态调用不同的行为。
  • 抽象状态(State)定义了一个接口,用于封装与上下文(Context)的一个特定状态相关的行为。
  • 具体状态(ConcreteState)实现了抽象状态(State)接口,并且包含了与该状态相关的行为。

状态模式的核心思想是将状态的变化封装到状态类中,使得状态的变化对于上下文(Context)对象来说是透明的。当状态发生变化时,上下文(Context)对象会自动切换到新的状态,并且调用新状态的行为。

状态模式的优点包括:

  1. 将状态的变化封装到状态类中,使得状态的变化对于上下文(Context)对象来说是透明的。
  2. 避免了大量的if-else语句,使得代码更加简洁和易于维护。
  3. 可以让状态的变化更加灵活和可扩展。

状态模式的缺点包括:

  1. 增加了类的数量,使得代码更加复杂。
  2. 如果状态转换比较复杂,可能会导致状态类之间的相互依赖性增加,从而影响代码的可维护性。

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

  1. 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时。
  2. 当一个对象需要根据它的状态来改变它的行为,并且它有很多状态时。
  3. 当一个对象的状态转换比较复杂时,可以使用状态模式来简化状态转换的过程。

(二)有限状态机

状态模式与有限状态机 的概念紧密相关。状态模式是状态机的一种实现方式。

状态机又叫有限状态机,Finite State Machine,FSM它有3部分组成:状态、事件、动作。其中事件也称为转移条件。事件触发状态的转移机动作的执行。不过,动作不是必须的,也可能只转移状态,不执行任何动作。

针对状态机的实现方式有:分支逻辑法、查表法和状态模式。

  • 分支逻辑法:使用if-else语句来判断当前状态,并根据状态执行相应的操作。这种方式实现简单,但是当状态较多时,会导致代码复杂度增加,不易维护。
  • 查表法:使用二维数组或哈希表来存储状态转移表,根据当前状态和输入条件查找对应的下一个状态。这种方式实现相对简单,但是需要事先构建状态转移表,当状态较多时,表格会变得很大,不易维护。
  • 状态模式:将状态封装成不同的类,每个状态类实现自己的行为,并且包含了状态转移的逻辑。上下文对象持有当前状态对象,当状态发生变化时,上下文对象会自动切换到新的状态,并且调用新状态的行为。这种方式实现了状态和行为的分离,代码结构清晰,易于维护和扩展。

总的来说,分支逻辑法和查表法都是基于条件判断的方式实现状态机,适用于状态较少的情况。而状态模式则是一种更加灵活和可扩展的方式,适用于状态较多或状态转移较为复杂的情况。

二、应用代码举例

(一)状态模式应用举例

电商业务场景中,订单状态是一个典型的状态机。订单可以处于不同的状态,如待支付、已支付、待发货、已发货、已完成等。每个状态都有自己的行为和状态转移规则。下面以订单状态为例,介绍状态模式的实现案例。

抽象状态类OrderState

首先,我们定义一个抽象状态类OrderState,它包含了订单状态的基本行为和状态转移方法:

  1. package org.zyf.javabasic.designpatterns.state;
  2. /**
  3. * @author yanfengzhang
  4. * @description 定义一个抽象状态类OrderState,它包含了订单状态的基本行为和状态转移方法
  5. * @date 2020/5/24 23:14
  6. */
  7. public abstract class OrderState {
  8. protected PDDOrder order;
  9. public OrderState(PDDOrder order) {
  10. this.order = order;
  11. }
  12. public abstract void pay();
  13. public abstract void cancel();
  14. public abstract void ship();
  15. public abstract void confirm();
  16. }

然后,我们定义具体状态类,如待支付状态、已支付状态、待发货状态、已发货状态和已完成状态。每个具体状态类实现自己的行为和状态转移方法。

具体状态类WaitingForPayState

  1. package org.zyf.javabasic.designpatterns.state;
  2. /**
  3. * @author yanfengzhang
  4. * @description 等待支付状态处理
  5. * @date 2020/5/24 23:19
  6. */
  7. public class WaitingForPayState extends OrderState{
  8. public WaitingForPayState(PDDOrder order) {
  9. super(order);
  10. }
  11. @Override
  12. public void pay() {
  13. System.out.println("订单已支付");
  14. order.setState(new PaidState(order));
  15. }
  16. @Override
  17. public void cancel() {
  18. System.out.println("订单已取消");
  19. order.setState(new CancelledState(order));
  20. }
  21. @Override
  22. public void ship() {
  23. System.out.println("订单未支付,不能发货");
  24. }
  25. @Override
  26. public void confirm() {
  27. System.out.println("订单未支付,不能确认收货");
  28. }
  29. }

具体状态类PaidState

  1. package org.zyf.javabasic.designpatterns.state;
  2. /**
  3. * @author yanfengzhang
  4. * @description
  5. * @date 2020/5/24 23:25
  6. */
  7. public class PaidState extends OrderState{
  8. public PaidState(PDDOrder order) {
  9. super(order);
  10. }
  11. @Override
  12. public void pay() {
  13. System.out.println("订单已支付,无需重复支付");
  14. }
  15. @Override
  16. public void cancel() {
  17. System.out.println("订单已取消");
  18. order.setState(new CancelledState(order));
  19. }
  20. @Override
  21. public void ship() {
  22. System.out.println("订单已发货");
  23. order.setState(new ShippedState(order));
  24. }
  25. @Override
  26. public void confirm() {
  27. System.out.println("订单已支付,确认收货成功");
  28. order.setState(new CompletedState(order));
  29. }
  30. }

具体状态类ShippedState

  1. package org.zyf.javabasic.designpatterns.state;
  2. /**
  3. * @author yanfengzhang
  4. * @description
  5. * @date 2020/5/24 23:27
  6. */
  7. public class ShippedState extends OrderState{
  8. public ShippedState(PDDOrder order) {
  9. super(order);
  10. }
  11. @Override
  12. public void pay() {
  13. System.out.println("订单已发货,不能支付");
  14. }
  15. @Override
  16. public void cancel() {
  17. System.out.println("订单已发货,不能取消");
  18. }
  19. @Override
  20. public void ship() {
  21. System.out.println("订单已发货,不能重复发货");
  22. }
  23. @Override
  24. public void confirm() {
  25. System.out.println("订单已发货,确认收货成功");
  26. order.setState(new CompletedState(order));
  27. }
  28. }

具体状态类CompletedState

  1. package org.zyf.javabasic.designpatterns.state;
  2. /**
  3. * @author yanfengzhang
  4. * @description
  5. * @date 2020/5/24 23:28
  6. */
  7. public class CompletedState extends OrderState{
  8. public CompletedState(PDDOrder order) {
  9. super(order);
  10. }
  11. @Override
  12. public void pay() {
  13. System.out.println("订单已完成,不能支付");
  14. }
  15. @Override
  16. public void cancel() {
  17. System.out.println("订单已完成,不能取消");
  18. }
  19. @Override
  20. public void ship() {
  21. System.out.println("订单已完成,不能发货");
  22. }
  23. @Override
  24. public void confirm() {
  25. System.out.println("订单已完成,不能重复确认收货");
  26. }
  27. }

具体状态类CancelledState

  1. package org.zyf.javabasic.designpatterns.state;
  2. /**
  3. * @author yanfengzhang
  4. * @description
  5. * @date 2020/5/24 23:29
  6. */
  7. public class CancelledState extends OrderState {
  8. public CancelledState(PDDOrder order) {
  9. super(order);
  10. }
  11. @Override
  12. public void pay() {
  13. System.out.println("订单已取消,不能支付");
  14. }
  15. @Override
  16. public void cancel() {
  17. System.out.println("订单已取消,不能重复取消");
  18. }
  19. @Override
  20. public void ship() {
  21. System.out.println("订单已取消,不能发货");
  22. }
  23. @Override
  24. public void confirm() {
  25. System.out.println("订单已取消,不能确认收货");
  26. }
  27. }

上下文类PDDOrder

定义上下文类PDDOrder,它持有当前订单状态对象,并且提供了一些操作方法

  1. package org.zyf.javabasic.designpatterns.state;
  2. /**
  3. * @author yanfengzhang
  4. * @description 定义上下文类
  5. * @date 2020/5/24 23:17
  6. */
  7. public class PDDOrder {
  8. private OrderState state;
  9. public PDDOrder() {
  10. state = new WaitingForPayState(this);
  11. }
  12. public void setState(OrderState state) {
  13. this.state = state;
  14. }
  15. public void pay() {
  16. state.pay();
  17. }
  18. public void cancel() {
  19. state.cancel();
  20. }
  21. public void ship() {
  22. state.ship();
  23. }
  24. public void confirm() {
  25. state.confirm();
  26. }
  27. }

测试验证

测试代码如下

  1. package org.zyf.javabasic.designpatterns.state;
  2. /**
  3. * @author yanfengzhang
  4. * @description
  5. * @date 2020/5/24 23:35
  6. */
  7. public class OrderStateTest {
  8. public static void main(String[] args) {
  9. PDDOrder order = new PDDOrder();
  10. // 初始状态为WaitingForPayState
  11. order.ship(); // 输出:"订单未支付,不能发货"
  12. order.confirm(); // 输出:"订单未支付,不能确认收货"
  13. order.cancel(); // 输出:"订单已取消"
  14. order.pay(); // 输出:"订单已支付"
  15. order.pay(); // 输出:"订单已支付,无需重复支付"
  16. order.cancel(); // 输出:"订单已取消"
  17. order.pay(); // 输出:"订单已支付"
  18. order.ship(); // 输出:"订单已发货"
  19. order.confirm(); // 输出:"订单已支付,确认收货成功"
  20. order.confirm(); // 输出:"订单已完成,不能重复确认收货"
  21. order.ship(); // 输出:"订单已完成,不能发货"
  22. order.cancel(); // 输出:"订单已完成,不能取消"
  23. }
  24. }

验证结果展示

  1. 订单未支付,不能发货
  2. 订单未支付,不能确认收货
  3. 订单已取消
  4. 订单已取消,不能支付
  5. 订单已取消,不能支付
  6. 订单已取消,不能重复取消
  7. 订单已取消,不能支付
  8. 订单已取消,不能发货
  9. 订单已取消,不能确认收货
  10. 订单已取消,不能确认收货
  11. 订单已取消,不能发货
  12. 订单已取消,不能重复取消

(二)分支逻辑法应用举例

分支逻辑法:使用if-else语句来判断当前状态,并根据状态执行相应的操作。这种方式实现简单,但是当状态较多时,会导致代码复杂度增加,不易维护。

以下是使用分支逻辑法实现的电商订单状态机代码。

订单状态State常量定义

  1. package org.zyf.javabasic.designpatterns.state.statemachine;
  2. /**
  3. * @author yanfengzhang
  4. * @description 订单相关常量处理
  5. * @date 2020/5/24 23:46
  6. */
  7. public class OrderCons {
  8. public static final class State {
  9. public static final int WAITING_FOR_PAY = 1;
  10. public static final int PAID = 2;
  11. public static final int SHIPPED = 3;
  12. public static final int CONFIRMED = 4;
  13. public static final int CANCELED = 5;
  14. }
  15. }

分支逻辑法处理OrderStatusMachineForFZ

  1. package org.zyf.javabasic.designpatterns.state.statemachine;
  2. /**
  3. * @author yanfengzhang
  4. * @description 使用分支逻辑法实现的电商订单状态机代码
  5. * @date 2020/5/24 23:19
  6. */
  7. public class OrderStatusMachineForFZ {
  8. private int state;
  9. public OrderStatusMachineForFZ() {
  10. this.state = OrderCons.State.WAITING_FOR_PAY;
  11. }
  12. public void pay() {
  13. if (state == OrderCons.State.WAITING_FOR_PAY) {
  14. System.out.println("订单已支付");
  15. state = OrderCons.State.PAID;
  16. } else if (state == OrderCons.State.PAID) {
  17. System.out.println("订单已支付,无需重复支付");
  18. } else if (state == OrderCons.State.SHIPPED) {
  19. System.out.println("订单已发货,不能支付");
  20. } else if (state == OrderCons.State.CONFIRMED) {
  21. System.out.println("订单已完成,不能支付");
  22. } else if (state == OrderCons.State.CANCELED) {
  23. System.out.println("订单已取消,不能支付");
  24. }
  25. }
  26. public void ship() {
  27. if (state == OrderCons.State.WAITING_FOR_PAY) {
  28. System.out.println("订单未支付,不能发货");
  29. } else if (state == OrderCons.State.PAID) {
  30. System.out.println("订单已发货");
  31. state = OrderCons.State.SHIPPED;
  32. } else if (state == OrderCons.State.SHIPPED) {
  33. System.out.println("订单已发货,不能重复发货");
  34. } else if (state == OrderCons.State.CONFIRMED) {
  35. System.out.println("订单已完成,不能发货");
  36. } else if (state == OrderCons.State.CANCELED) {
  37. System.out.println("订单已取消,不能发货");
  38. }
  39. }
  40. public void confirm() {
  41. if (state == OrderCons.State.WAITING_FOR_PAY) {
  42. System.out.println("订单未支付,不能确认收货");
  43. } else if (state == OrderCons.State.PAID) {
  44. System.out.println("订单已支付,不能确认收货");
  45. } else if (state == OrderCons.State.SHIPPED) {
  46. System.out.println("订单已支付,确认收货成功");
  47. state = OrderCons.State.CONFIRMED;
  48. } else if (state == OrderCons.State.CONFIRMED) {
  49. System.out.println("订单已完成,不能重复确认收货");
  50. } else if (state == OrderCons.State.CANCELED) {
  51. System.out.println("订单已取消,不能确认收货");
  52. }
  53. }
  54. public void cancel() {
  55. if (state == OrderCons.State.WAITING_FOR_PAY) {
  56. System.out.println("订单已取消");
  57. state = OrderCons.State.CANCELED;
  58. } else if (state == OrderCons.State.PAID) {
  59. System.out.println("订单已支付,不能取消");
  60. } else if (state == OrderCons.State.SHIPPED) {
  61. System.out.println("订单已发货,不能取消");
  62. } else if (state == OrderCons.State.CONFIRMED) {
  63. System.out.println("订单已完成,不能取消");
  64. } else if (state == OrderCons.State.CANCELED) {
  65. System.out.println("订单已取消,不能重复取消");
  66. }
  67. }
  68. }

测试验证

测试代码如下

  1. package org.zyf.javabasic.designpatterns.state.statemachine;
  2. /**
  3. * @author yanfengzhang
  4. * @description
  5. * @date 2020/5/24 23:22
  6. */
  7. public class OrderStatusMachineForFZTest {
  8. public static void main(String[] args) {
  9. OrderStatusMachineForFZ order = new OrderStatusMachineForFZ();
  10. // 初始状态为WAITING_FOR_PAY
  11. order.ship(); // 输出:"订单未支付,不能发货"
  12. order.confirm(); // 输出:"订单未支付,不能确认收货"
  13. order.cancel(); // 输出:"订单已取消"
  14. order.pay(); // 输出:"订单已支付"
  15. order.pay(); // 输出:"订单已支付,无需重复支付"
  16. order.cancel(); // 输出:"订单已取消"
  17. order.pay(); // 输出:"订单已支付"
  18. order.ship(); // 输出:"订单已发货"
  19. order.confirm(); // 输出:"订单已支付,确认收货成功"
  20. order.confirm(); // 输出:"订单已完成,不能重复确认收货"
  21. order.ship(); // 输出:"订单已完成,不能发货"
  22. order.cancel(); // 输出:"订单已完成,不能取消"
  23. }
  24. }

验证结果展示

  1. 订单未支付,不能发货
  2. 订单未支付,不能确认收货
  3. 订单已取消
  4. 订单已取消,不能支付
  5. 订单已取消,不能支付
  6. 订单已取消,不能重复取消
  7. 订单已取消,不能支付
  8. 订单已取消,不能发货
  9. 订单已取消,不能确认收货
  10. 订单已取消,不能确认收货
  11. 订单已取消,不能发货
  12. 订单已取消,不能重复取消

(三)查表法应用举例

查表法:使用二维数组或哈希表来存储状态转移表,根据当前状态和输入条件查找对应的下一个状态。这种方式实现相对简单,但是需要事先构建状态转移表,当状态较多时,表格会变得很大,不易维护。

以下是一个简单的咖啡自动售货机状态机的示例代码。

订单状态与事件常量定义

  1. private static class State {
  2. private static final int IDLE = 0;
  3. private static final int MAKING_COFFEE = 1;
  4. private static final int DISPENSING_COFFEE = 2;
  5. }
  6. private static class Event {
  7. private static final int INSERT_COIN = 0;
  8. private static final int MAKE_COFFEE = 1;
  9. private static final int DISPENSE_COFFEE = 2;
  10. }

查表法处理CoffeeVendingMachine

  1. package org.zyf.javabasic.designpatterns.state.statemachine;
  2. /**
  3. * @author yanfengzhang
  4. * @description
  5. * @date 2020/5/24 23:58
  6. */
  7. public class CoffeeVendingMachine {
  8. private int[][] transitionTable = {
  9. // IDLE
  10. {State.MAKING_COFFEE, State.IDLE},
  11. // MAKING_COFFEE
  12. {State.DISPENSING_COFFEE, State.MAKING_COFFEE},
  13. // DISPENSING_COFFEE
  14. {State.IDLE, State.IDLE}
  15. };
  16. private int state;
  17. public CoffeeVendingMachine() {
  18. this.state = State.IDLE;
  19. }
  20. public void insertCoin() {
  21. if (state == State.IDLE) {
  22. state = transitionTable[state][Event.INSERT_COIN];
  23. System.out.println("请按下咖啡按钮");
  24. } else {
  25. System.out.println("当前状态无法插入硬币");
  26. }
  27. }
  28. public void makeCoffee() {
  29. if (state == State.MAKING_COFFEE) {
  30. state = transitionTable[state][Event.MAKE_COFFEE];
  31. System.out.println("正在制作咖啡,请稍等");
  32. } else {
  33. System.out.println("当前状态无法制作咖啡");
  34. }
  35. }
  36. public void dispenseCoffee() {
  37. if (state == State.DISPENSING_COFFEE) {
  38. state = transitionTable[state][Event.DISPENSE_COFFEE];
  39. System.out.println("请取走您的咖啡");
  40. } else {
  41. System.out.println("当前状态无法取走咖啡");
  42. }
  43. }
  44. private static class State {
  45. private static final int IDLE = 0;
  46. private static final int MAKING_COFFEE = 1;
  47. private static final int DISPENSING_COFFEE = 2;
  48. }
  49. private static class Event {
  50. private static final int INSERT_COIN = 0;
  51. private static final int MAKE_COFFEE = 1;
  52. private static final int DISPENSE_COFFEE = 2;
  53. }
  54. }

我们定义了一个咖啡自动售货机状态机,它有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。

使用状态机可以帮助我们更好地管理咖啡自动售货机的状态,避免了一些状态转换的错误。

测试验证

测试代码如下

  1. package org.zyf.javabasic.designpatterns.state.statemachine;
  2. /**
  3. * @author yanfengzhang
  4. * @description
  5. * @date 2020/5/24 23:59
  6. */
  7. public class CoffeeVendingMachineTest {
  8. public static void main(String[] args) {
  9. CoffeeVendingMachine vendingMachine = new CoffeeVendingMachine();
  10. vendingMachine.insertCoin();
  11. vendingMachine.makeCoffee();
  12. vendingMachine.dispenseCoffee();
  13. }
  14. }

验证结果展示

  1. 请按下咖啡按钮
  2. 正在制作咖啡,请稍等
  3. 当前状态无法取走咖啡

三、Spring-statemachine状态机框架

(一)基本介绍

spring-statemachine是一个基于Spring框架的状态机框架,它提供了一种方便的方式来实现状态机,并且可以与Spring框架无缝集成。使用spring-statemachine可以帮助我们更方便地实现状态机,并且可以提高代码的可读性和可维护性。

spring-statemachine提供了以下功能:

  • 状态机的定义和管理:可以通过定义状态、事件、转换和动作等来管理状态机。
  • 状态机的启动和停止:可以通过启动和停止状态机来控制状态机的运行。
  • 状态机的状态转换:可以通过发送事件来触发状态机的状态转换。
  • 状态机的监听器:可以通过监听器来监控状态机的状态变化和事件处理过程。
  • 状态机的条件判断:可以通过Guard来对事件进行条件判断,避免不符合条件的事件被处理。
  • 状态机的动作处理:可以通过动作来处理状态机的状态转换过程中的业务逻辑。
  • 状态机的持久化:可以通过扩展依赖来实现状态机的持久化,如使用JPA存储状态机。

总之,spring-statemachine提供了一种方便的方式来实现状态机,并且可以与Spring框架无缝集成。使用spring-statemachine可以帮助我们更方便地实现状态机,并且可以提高代码的可读性和可维护性。

(二)应用举例

项目中引入相应的依赖

  1. org.springframework.statemachine
  2. spring-statemachine-core
  3. 3.2.0
  4. com.vaadin.external.google
  5. android-json
  6. io.projectreactor
  7. reactor-core
  8. 3.4.0

应用举例

spring-statemachine提供了一些接口和类,可以帮助我们定义状态、事件、转换和动作,并且可以自动管理状态机的状态。以下是一个简单的示例:

首先,我们需要定义状态、事件和转换:

  1. package org.zyf.javabasic.designpatterns.state.statemachine.spring;
  2. /**
  3. * @author yanfengzhang
  4. * @description
  5. * @date 2020/5/26 23:13
  6. */
  7. public enum States {
  8. IDLE,
  9. MAKING_COFFEE,
  10. DISPENSING_COFFEE
  11. }
  12. package org.zyf.javabasic.designpatterns.state.statemachine.spring;
  13. /**
  14. * @author yanfengzhang
  15. * @description
  16. * @date 2020/5/26 23:14
  17. */
  18. public enum Events {
  19. INSERT_COIN,
  20. MAKE_COFFEE,
  21. DISPENSE_COFFEE
  22. }

在这个示例中,我们定义了3个状态:IDLE、MAKING_COFFEE和DISPENSING_COFFEE,以及3个事件:INSERT_COIN、MAKE_COFFEE和DISPENSE_COFFEE。

接着,使用@Configuration和@EnableStateMachine注解来告诉Spring框架这是一个状态机配置类,并且启用状态机。

  1. package org.zyf.javabasic.designpatterns.state.statemachine.spring;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.statemachine.config.EnableStateMachine;
  4. import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
  5. import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
  6. import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
  7. import java.util.EnumSet;
  8. /**
  9. * @author yanfengzhang
  10. * @description 状态机配置类
  11. * @date 2020/5/26 23:15
  12. */
  13. @Configuration
  14. @EnableStateMachine
  15. public class CoffeeStateMachineConfig extends EnumStateMachineConfigurerAdapter {
  16. @Override
  17. public void configure(StateMachineStateConfigurer states) throws Exception {
  18. states
  19. .withStates()
  20. .initial(States.IDLE)
  21. .states(EnumSet.allOf(States.class));
  22. }
  23. @Override
  24. public void configure(StateMachineTransitionConfigurer transitions) throws Exception {
  25. transitions
  26. .withExternal()
  27. .source(States.IDLE).target(States.MAKING_COFFEE)
  28. .event(Events.INSERT_COIN)
  29. .and()
  30. .withExternal()
  31. .source(States.MAKING_COFFEE).target(States.DISPENSING_COFFEE)
  32. .event(Events.MAKE_COFFEE)
  33. .and()
  34. .withExternal()
  35. .source(States.DISPENSING_COFFEE).target(States.IDLE)
  36. .event(Events.DISPENSE_COFFEE);
  37. }
  38. }

然后,我们可以定义状态机的动作:

  1. package org.zyf.javabasic.designpatterns.state.statemachine.spring;
  2. import org.springframework.messaging.Message;
  3. import org.springframework.statemachine.listener.StateMachineListenerAdapter;
  4. import org.springframework.statemachine.state.State;
  5. /**
  6. * @author yanfengzhang
  7. * @description 定义状态机的动作
  8. * 定义了一个状态机监听器,它可以在状态机状态发生变化或者事件不被接受时输出一些信息
  9. * @date 2020/5/26 23:17
  10. */
  11. public class CoffeeStateMachineListener extends StateMachineListenerAdapter {
  12. @Override
  13. public void stateChanged(State from, State to) {
  14. System.out.println("状态从 " + from + " 变为 " + to);
  15. }
  16. @Override
  17. public void eventNotAccepted(Message event) {
  18. System.out.println("事件 " + event.getPayload() + " 不被接受");
  19. }
  20. }

在这个示例中,我们定义了一个状态机监听器,它可以在状态机状态发生变化或者事件不被接受时输出一些信息。

代码验证

测试代码

  1. package org.zyf.javabasic.designpatterns.state.statemachine.spring;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.junit.Test;
  4. import org.junit.runner.RunWith;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.boot.test.context.SpringBootTest;
  7. import org.springframework.statemachine.StateMachine;
  8. import org.springframework.test.context.junit4.SpringRunner;
  9. import org.zyf.javabasic.ZYFApplication;
  10. /**
  11. * @author yanfengzhang
  12. * @description
  13. * @date 2020/5/26 23:22
  14. */
  15. @RunWith(SpringRunner.class)
  16. @SpringBootTest(classes = ZYFApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
  17. @Slf4j
  18. public class CoffeeStateMachineApplicationTest {
  19. @Autowired
  20. private StateMachine stateMachine;
  21. @Test
  22. public void testStateMachine() {
  23. stateMachine.start();
  24. System.out.println(stateMachine.getState().getId());
  25. stateMachine.sendEvent(Events.INSERT_COIN);
  26. System.out.println(stateMachine.getState().getId());
  27. stateMachine.sendEvent(Events.MAKE_COFFEE);
  28. System.out.println(stateMachine.getState().getId());
  29. stateMachine.sendEvent(Events.DISPENSE_COFFEE);
  30. System.out.println(stateMachine.getState().getId());
  31. }
  32. }

验证结果

  1. IDLE
  2. MAKING_COFFEE
  3. DISPENSING_COFFEE
  4. IDLE

(三)使用建议

在使用spring-statemachine开发过程中,有以下几点建议:

  • 状态机的设计应该尽可能简单和清晰,避免出现复杂的状态转换逻辑,这样可以降低代码的复杂度和维护成本。
  • 将状态机的状态、事件、转换和动作等定义在一个单独的类中,这样可以方便地进行管理和修改。
  • 使用状态机监听器来监控状态机的状态变化和事件处理过程,这样可以方便地调试和排查问题。
  • 在状态机的转换过程中,可以使用Guard来对事件进行条件判断,避免不符合条件的事件被处理。
  • 在状态机的动作中,应该尽可能避免进行复杂的业务逻辑处理,可以将业务逻辑处理放在状态机之外,这样可以提高状态机的可重用性和可测试性。
  • 在使用spring-statemachine时,应该注意版本的兼容性,避免出现不兼容的情况。

总之,使用spring-statemachine可以帮助我们更方便地实现状态机,并且可以与Spring框架无缝集成。在开发过程中,我们应该尽可能简化状态机的设计,使用监听器来监控状态机的状态变化和事件处理过程,避免在状态机中进行复杂的业务逻辑处理。

四、实际线上应用案例

(一)业务背景介绍

“邀请下单”是美团外卖用户邀请其他用户下单后给予奖励的平台。即用户A邀请用户B,并且用户B在美团下单后,给予用户A一定的现金奖励(以下简称返奖)。同时为了协调成本与收益的关系,返奖会有多个计算策略。邀请下单后台主要涉及两个技术要点:

  1. 返奖金额的计算,涉及到不同的计算规则。
  2. 从邀请开始到返奖结束的整个流程。

后续重点分析返奖流程与设计模式实践

(二)返奖流程与设计模式实践

业务建模

当受邀人在接受邀请人的邀请并且下单后,返奖后台接收到受邀人的下单记录,此时邀请人也进入返奖流程。首先我们订阅用户订单消息并对订单进行返奖规则校验。例如,是否使用红包下单,是否在红包有效期内下单,订单是否满足一定的优惠金额等等条件。当满足这些条件以后,我们将订单信息放入延迟队列中进行后续处理。经过T+N天之后处理该延迟消息,判断用户是否对该订单进行了退款,如果未退款,对用户进行返奖。若返奖失败,后台还有返奖补偿流程,再次进行返奖。其流程如下图所示:

我们对上述业务流程进行领域建模:

  1. 在接收到订单消息后,用户进入待校验状态;
  2. 在校验后,若校验通过,用户进入预返奖状态,并放入延迟队列。若校验未通过,用户进入不返奖状态,结束流程;
  3. T+N天后,处理延迟消息,若用户未退款,进入待返奖状态。若用户退款,进入失败状态,结束流程;
  4. 执行返奖,若返奖成功,进入完成状态,结束流程。若返奖不成功,进入待补偿状态;
  5. 待补偿状态的用户会由任务定期触发补偿机制,直至返奖成功,进入完成状态,保障流程结束。

可以看到,我们通过建模将返奖流程的多个步骤映射为系统的状态。对于系统状态的表述,DDD中常用到的概念是领域事件,另外也提及过事件溯源的实践方案。当然,在设计模式中,也有一种能够表述系统状态的代码模型,那就是状态模式。在邀请下单系统中,我们的主要流程是返奖。对于返奖,每一个状态要进行的动作和操作都是不同的。因此,使用状态模式,能够帮助我们对系统状态以及状态间的流转进行统一的管理和扩展。

模式:状态模式

模式定义:当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。

状态模式的通用类图如下图所示

对比策略模式的类型会发现和状态模式的类图很类似,但实际上有很大的区别,具体体现在concrete class上。策略模式通过Context产生唯一一个ConcreteStrategy作用于代码中,而状态模式则是通过context组织多个ConcreteState形成一个状态转换图来实现业务逻辑。接下来,我们通过一段通用代码来解释怎么使用状态模式:

  1. //定义一个抽象的状态类
  2. public abstract class State {
  3. Context context;
  4. public void setContext(Context context) {
  5. this.context = context;
  6. }
  7. public abstract void handle1();
  8. public abstract void handle2();
  9. }
  10. //定义状态A
  11. public class ConcreteStateA extends State {
  12. @Override
  13. public void handle1() {} //本状态下必须要处理的事情
  14. ​
  15. @Override
  16. public void handle2() {
  17. super.context.setCurrentState(Context.contreteStateB); //切换到状态B
  18. super.context.handle2(); //执行状态B的任务
  19. }
  20. }
  21. //定义状态B
  22. public class ConcreteStateB extends State {
  23. @Override
  24. public void handle2() {} //本状态下必须要处理的事情,...
  25. @Override
  26. public void handle1() {
  27. super.context.setCurrentState(Context.contreteStateA); //切换到状态A
  28. super.context.handle1(); //执行状态A的任务
  29. }
  30. }
  31. //定义一个上下文管理环境
  32. public class Context {
  33. public final static ConcreteStateA contreteStateA = new ConcreteStateA();
  34. public final static ConcreteStateB contreteStateB = new ConcreteStateB();
  35. ​
  36. private State CurrentState;
  37. public State getCurrentState() {return CurrentState;}
  38. ​
  39. public void setCurrentState(State currentState) {
  40. this.CurrentState = currentState;
  41. this.CurrentState.setContext(this);
  42. }
  43. ​
  44. public void handle1() {this.CurrentState.handle1();}
  45. public void handle2() {this.CurrentState.handle2();}
  46. }
  47. //定义client执行
  48. public class client {
  49. public static void main(String[] args) {
  50. Context context = new Context();
  51. context.setCurrentState(new ContreteStateA());
  52. context.handle1();
  53. context.handle2();
  54. }
  55. }

工程实践

通过前文对状态模式的简介,我们可以看到当状态之间的转换在不是非常复杂的情况下,通用的状态模式存在大量的与状态无关的动作从而产生大量的无用代码。在我们的实践中,一个状态的下游不会涉及特别多的状态装换,所以我们简化了状态模式。当前的状态只负责当前状态要处理的事情,状态的流转则由第三方类负责。其实践代码如下:

  1. //返奖状态执行的上下文
  2. public class RewardStateContext {
  3. ​
  4. private RewardState rewardState;
  5. public void setRewardState(RewardState currentState) {this.rewardState = currentState;}
  6. public RewardState getRewardState() {return rewardState;}
  7. public void echo(RewardStateContext context, Request request) {
  8. rewardState.doReward(context, request);
  9. }
  10. }
  11. ​
  12. public abstract class RewardState {
  13. abstract void doReward(RewardStateContext context, Request request);
  14. }
  15. ​
  16. //待校验状态
  17. public class OrderCheckState extends RewardState {
  18. @Override
  19. public void doReward(RewardStateContext context, Request request) {
  20. orderCheck(context, request); //对进来的订单进行校验,判断是否用券,是否满足优惠条件等等
  21. }
  22. }
  23. ​
  24. //待补偿状态
  25. public class CompensateRewardState extends RewardState {
  26. @Override
  27. public void doReward(RewardStateContext context, Request request) {
  28. compensateReward(context, request); //返奖失败,需要对用户进行返奖补偿
  29. }
  30. }
  31. ​
  32. //预返奖状态,待返奖状态,成功状态,失败状态(此处逻辑省略)
  33. //..
  34. ​
  35. public class InviteRewardServiceImpl {
  36. public boolean sendRewardForInvtee(long userId, long orderId) {
  37. Request request = new Request(userId, orderId);
  38. RewardStateContext rewardContext = new RewardStateContext();
  39. rewardContext.setRewardState(new OrderCheckState());
  40. rewardContext.echo(rewardContext, request); //开始返奖,订单校验
  41. //此处的if-else逻辑只是为了表达状态的转换过程,并非实际的业务逻辑
  42. if (rewardContext.isResultFlag()) { //如果订单校验成功,进入预返奖状态
  43. rewardContext.setRewardState(new BeforeRewardCheckState());
  44. rewardContext.echo(rewardContext, request);
  45. } else {//如果订单校验失败,进入返奖失败流程,...
  46. rewardContext.setRewardState(new RewardFailedState());
  47. rewardContext.echo(rewardContext, request);
  48. return false;
  49. }
  50. if (rewardContext.isResultFlag()) {//预返奖检查成功,进入待返奖流程,...
  51. rewardContext.setRewardState(new SendRewardState());
  52. rewardContext.echo(rewardContext, request);
  53. } else { //如果预返奖检查失败,进入返奖失败流程,...
  54. rewardContext.setRewardState(new RewardFailedState());
  55. rewardContext.echo(rewardContext, request);
  56. return false;
  57. }
  58. if (rewardContext.isResultFlag()) { //返奖成功,进入返奖结束流程,...
  59. rewardContext.setRewardState(new RewardSuccessState());
  60. rewardContext.echo(rewardContext, request);
  61. } else { //返奖失败,进入返奖补偿阶段,...
  62. rewardContext.setRewardState(new CompensateRewardState());
  63. rewardContext.echo(rewardContext, request);
  64. }
  65. if (rewardContext.isResultFlag()) { //补偿成功,进入返奖完成阶段,...
  66. rewardContext.setRewardState(new RewardSuccessState());
  67. rewardContext.echo(rewardContext, request);
  68. } else { //补偿失败,仍然停留在当前态,直至补偿成功(或多次补偿失败后人工介入处理)
  69. rewardContext.setRewardState(new CompensateRewardState());
  70. rewardContext.echo(rewardContext, request);
  71. }
  72. return true;
  73. }
  74. }

状态模式的核心是封装,将状态以及状态转换逻辑封装到类的内部来实现,也很好的体现了“开闭原则”和“单一职责原则”。每一个状态都是一个子类,不管是修改还是增加状态,只需要修改或者增加一个子类即可。在我们的应用场景中,状态数量以及状态转换远比上述例子复杂,通过“状态模式”避免了大量的if-else代码,让我们的逻辑变得更加清晰。同时由于状态模式的良好的封装性以及遵循的设计原则,让我们在复杂的业务场景中,能够游刃有余地管理各个状态。

参考文章链接

状态设计模式

状态模式(State Pattern) - 简书

设计模式在外卖营销业务中的实践 - 美团技术团队

注:本文转载自blog.csdn.net的张彦峰ZYF的文章"https://zyfcodes.blog.csdn.net/article/details/106080007"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top