本文节选自《设计模式就该这样学》

1 状态模式的UML类图

状态模式的UML类图如下图所示。

2 使用状态模式实现登录状态自由切换

当我们在社区阅读文章时,如果觉得文章写得很好,我们就会评论、收藏两连发。如果处于登录情况下,则可以直接做评论、收藏这些行为。否则,跳转到登录界面,登录后再继续执行先前的动作。这里涉及的状态有两种:登录与未登录;行为有两种:评论和收藏。下面使用状态模式来实现这个逻辑,代码如下。

首先创建抽象状态角色UserState类。


public abstract class UserState {
protected AppContext context; public void setContext(AppContext context) {
this.context = context;
} public abstract void favorite(); public abstract void comment(String comment);
}

然后创建登录状态LogInState类。


public class LoginInState extends UserState {
@Override
public void favorite() {
System.out.println("收藏成功!");
} @Override
public void comment(String comment) {
System.out.println(comment);
}
}

创建未登录状态UnloginState类。


public class UnLoginState extends UserState {
@Override
public void favorite() {
this.switch2Login();
this.context.getState().favorite();
} @Override
public void comment(String comment) {
this.switch2Login();
this.context.getState().comment(comment);
} private void switch2Login() {
System.out.println("跳转到登录页面!");
this.context.setState(this.context.STATE_LOGIN);
}
}

创建上下文角色AppContext类。


public class AppContext {
public static final UserState STATE_LOGIN = new LoginInState();
public static final UserState STATE_UNLOGIN = new UnLoginState();
private UserState currentState = STATE_UNLOGIN;
{
STATE_LOGIN.setContext(this);
STATE_UNLOGIN.setContext(this);
} public void setState(UserState state) {
this.currentState = state;
this.currentState.setContext(this);
} public UserState getState() {
return this.currentState;
} public void favorite() {
this.currentState.favorite();
} public void comment(String comment) {
this.currentState.comment(comment);
}
}

最后编写客户端测试代码。


public static void main(String[] args) {
AppContext context = new AppContext();
context.favorite();
context.comment("评论: 好文章,360个赞!");
}

运行结果如下图所示。

3 使用状态机实现订单状态流转控制

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

3.1 添加依赖。


<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-core</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>

3.2 创建订单实体Order类。


public class Order {
private int 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.3 创建订单状态枚举类和状态转换枚举类。


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

3.4 添加状态流转配置。


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

3.5 添加订单状态监听器。


@Component("orderStateListener")
@WithStateMachine(name = "orderStateMachine")
public class 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());
return true;
} @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());
return true;
} @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());
return true;
}
}

3.6 创建IOrderService接口。


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

3.7 在Service业务逻辑中应用。


@Service("orderService")
public class OrderServiceImpl implements IOrderService { @Autowired
private StateMachine<OrderStatus, OrderStatusChangeEvent> orderStateMachine; @Autowired
private StateMachinePersister<OrderStatus, OrderStatusChangeEvent, Order> persister; private int 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;
}
}

3.8 编写客户端测试代码。



@SpringBootApplication
public class Test {
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()); }
}

通过这个真实的业务案例,相信小伙伴们已经对状态模式有了一个非常深刻的理解。

关注微信公众号『 Tom弹架构 』回复“设计模式”可获取完整源码。

【推荐】Tom弹架构:30个设计模式真实案例(附源码),挑战年薪60W不是梦

本文为“Tom弹架构”原创,转载请注明出处。技术在于分享,我分享我快乐!

如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。关注微信公众号『 Tom弹架构 』可获取更多技术干货!

彻底搞懂Spring状态机原理,实现订单与物流解耦的更多相关文章

  1. 这一次搞懂Spring事务注解的解析

    前言 事务我们都知道是什么,而Spring事务就是在数据库之上利用AOP提供声明式事务和编程式事务帮助我们简化开发,解耦业务逻辑和系统逻辑.但是Spring事务原理是怎样?事务在方法间是如何传播的?为 ...

  2. 这一次搞懂SpringBoot核心原理(自动配置、事件驱动、Condition)

    @ 目录 前言 正文 启动原理 事件驱动 自动配置原理 Condition注解原理 总结 前言 SpringBoot是Spring的包装,通过自动配置使得SpringBoot可以做到开箱即用,上手成本 ...

  3. 一张图搞懂Spring bean的完整生命周期

    一张图搞懂Spring bean的生命周期,从Spring容器启动到容器销毁bean的全过程,包括下面一系列的流程,了解这些流程对我们想在其中任何一个环节怎么操作bean的生成及修饰是非常有帮助的. ...

  4. 这一次搞懂Spring事务是如何传播的

    文章目录 前言 正文 事务切面的调用过程 事务的传播性概念 实例分析 总结 前言 上一篇分析了事务注解的解析过程,本质上是将事务封装为切面加入到AOP的执行链中,因此会调用到MethodIncepto ...

  5. 一篇文章彻底搞懂base64编码原理

    开始 在互联网中的每一刻,你可能都在享受着Base64带来的便捷,但对于Base64的基础原理又了解多少?今天这篇文章带领大家了解一下Base64的底层实现. base64是什么东东呢? Base64 ...

  6. 五分钟学Java:一篇文章搞懂spring和springMVC

    原创声明 本文作者:黄小斜 转载请务必在文章开头注明出处和作者. 本文思维导图 什么是Spring,为什么你要学习spring? 你第一次接触spring框架是在什么时候?相信很多人和我一样,第一次了 ...

  7. 五分钟学Java:一篇文章带你搞懂spring全家桶套餐

    原创声明 本文首发于微信公众号[程序员黄小斜] 本文作者:黄小斜 转载请务必在文章开头注明出处和作者. 本文思维导图 什么是Spring,为什么你要学习spring? 你第一次接触spring框架是在 ...

  8. 这一次搞懂Spring代理创建及AOP链式调用过程

    文章目录 前言 正文 基本概念 代理对象的创建 小结 AOP链式调用 AOP扩展知识 一.自定义全局拦截器Interceptor 二.循环依赖三级缓存存在的必要性 三.如何在Bean创建之前提前创建代 ...

  9. 这一次搞懂Spring的Bean实例化原理

    文章目录 前言 正文 环境准备 两个重要的Processor 注册BeanPostProcessor对象 Bean对象的创建 createBeanInstance addSingletonFactor ...

随机推荐

  1. 从零入门 Serverless | SAE 场景下,应用流量的负载均衡及路由策略配置实践

    作者 | 落语 阿里云云原生技术团队 本文整理自<Serverless 技术公开课>,关注"Serverless"公众号,回复"入门",即可获取 S ...

  2. Editing Tools(编辑工具)

    编辑工具 # Process: 修剪线 arcpy.TrimLine_edit("", "", "DELETE_SHORT") # Proc ...

  3. SpringBoot下使用AspectJ(CTW)下不能注入SpringIOC容器中的Bean

    SpringBoot下使用AspectJ(CTW)下不能注入SpringIOC容器中的Bean 在SpringBoot中开发AspectJ时,使用CTW的方式来织入代码,由于采用这种形式,切面Bean ...

  4. pycharm输入代码后,没有补全提示

    安装pycharm后,输入代码后,没有补全提示 首先检查是否关闭了代码提示,如下图,将红框中"Power Save Mode"前的勾去掉 第二步,如果在输入某些代码时还是没有补全提 ...

  5. find+xargs+sed批量替换

    写代码时经常遇到要把 .c  和 .h的文件中的某些内容全部替换的情况,用sourceinsight 进行全局的查找是一个方法,但是sourceinsight只能替换一个文件中的字符串,不能同时替换多 ...

  6. [软工顶级理解组] 团队任务拆解(Alpha)

    一.任务概述 在alpha阶段,我们需要完成功能规格说明书中所提到的所有功能,在一个阶段的开发周期内,交付最小可行的可用版本. 二.任务分配及时长 分组&成员 具体任务 预计时长(小时) 前端 ...

  7. 2021.10.18考试总结[NOIP模拟76]

    T1 洛希极限 不难发现每个点肯定是被它上一行或上一列的点转移.可以预处理出每个点上一行,上一列最远的能转移到它的点,然后单调队列优化. 预处理稍显ex.可以用并查集维护一个链表,记录当前点之后第一个 ...

  8. CodeForces-1076E Vasya and a Tree

    CodeForces - 1076E Problem Description: Vasya has a tree consisting of n vertices with root in verte ...

  9. 前端大牛带你了解JavaScript 函数式编程

    前言 函数式编程在前端已经成为了一个非常热门的话题.在最近几年里,我们看到非常多的应用程序代码库里大量使用着函数式编程思想. 本文将略去那些晦涩难懂的概念介绍,重点展示在 JavaScript 中到底 ...

  10. (类)Program1.1

    1 class MyClass: 2 3 i = 12345 4 5 def __init__(self): 6 self.data = "WOOWOWOWO" 7 8 def f ...