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

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. Jmeter压测学习3---通过正则表达式提取token

    上一个随笔记录的是用json提取器提取token,这个随笔记录用正则表达式提取token 一.添加正则表达式 登录右击添加->后置处理器->正则表达式提取器 正则提取器参数说明: 要检查的 ...

  2. centos8安装MySQL8——通过yum

    centos8上通过yum安装MySQL,过程简单,不易出错 1.检查系统是否已安装MySQL相关,如果有则全部清除干净 #列出MySQL相关的安装包 rpm -qa | grep mysql #依次 ...

  3. js 判断两个对象是否相等

    最近碰到的一个面试题,不算高频,记录一下 判断两个对象是否相等,大致分为三步 首先判断两个比较对象是不是 Object 如果都是对象 再比较 对象的长度是否相等 如果两个对象的长度相等 再比较对象属性 ...

  4. Golang通脉之map

    Go语言中提供的映射关系容器为map,其内部使用散列表(hash)实现. map 是一种无序的键值对的集合.map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值 map ...

  5. 爬虫逆向基础,理解 JavaScript 模块化编程 webpack

    关注微信公众号:K哥爬虫,QQ交流群:808574309,持续分享爬虫进阶.JS/安卓逆向等技术干货! 简介 在分析一些站点的 JavaScript 代码时,比较简单的代码,函数通常都是一个一个的,例 ...

  6. 【二食堂】Beta - 事后分析

    事后分析 设想和目标 我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? Beta阶段我们首先要对文本标注方式进行优化,其次时添加好友系统,实现邀请好友共同标注的功能. ...

  7. LP-DDR 和其他 DDR

    一篇技術文檔比較 LP-DDR 和其他 DDR. 就觀念來說,LP-DDR 就是 Low Power 的 DDR:但就架構來說,LP-DDR 和其他 DDR 是截然不同的東西. 他們分屬不同的 JDE ...

  8. 洛谷 P6075 [JSOI2015]子集选取

    链接:P6075 前言: 虽然其他大佬们的走分界线的方法比我巧妙多了,但还是提供一种思路. 题意: %&¥--@#直接看题面理解罢. 分析过程: 看到这样的题面我脑里第一反应就是DP,但是看到 ...

  9. [WPF] 在 Windows 11 中处理 WindowChrome 的圆角

    1. Windows 11 的圆角 在直角统治了微软的 UI 设计多年以后,微软突然把直角骂了一顿,说还是圆角好看,于是 Windows 11 随处都可看到圆角设计.Windows 11 使用 3 个 ...

  10. shell 中小括号,中括号,大括号的区别

    一.小括号,圆括号() 1.单小括号 () ①命令组.括号中的命令将会新开一个子shell顺序执行,所以括号中的变量不能够被脚本余下的部分使用.括号中多个命令之间用分号隔开,最后一个命令可以没有分号, ...