这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战
欢迎来到今天的学习,今天我们一起来学习下你可能很少听说,但是随处可用的----状态模式。多唠叨几句,我本月将会对java的设计模式精讲,欢迎点击头像,关注我的专栏,我会持续更新,加油!
系列文章:
...持续更新中
话不多说,进入正题
状态模式
顾名思义,应用场景肯定是要随时改变某个事件业务的状态,比如我们的支付的订单订单,我们购买商品到用户收货完成的一系列,都可以用到状态模式。
它的原始定义是:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了自己的类一样。
简单来说,状态模式就是让一个对象通过定义一系列状态的变化来控制行为的变化,比如我们提到的线上商品购买。给购买的物品定义几个包裹运送状态,已下单、运送中、已签收等状态的调整,也就是说,当包裹的状态发生改变时,就会触发相应的外部操作。
我们看下图。大概就是这样子(此图来源于网络):
从图中,我们能看出状态模式包含的关键角色有三个:
-
上下文信息类(Context):实际上就是存储当前状态的类,对外提供更新状态的操作。(一般你看到Context字眼 上下文信息类,spring当中有很多类似的)
-
抽象状态类(State):可以是一个接口或抽象类,用于定义声明状态更新的操作方法有哪些
-
具体状态类(StateA 等):实现抽象状态类定义的方法,根据具体的场景来指定对应状态改变后的代码实现逻辑
接下来我们拿支付订单状态实际场景结合代码来深入理解下状态模式
代码展示
为了帮助你更好地理解状态模式的适用场景,下面我们还是通过一个简单的例子来演示一下。在用户支付一笔订单的过程中,当我们选定好了商品提交订单后,用户点击支付,拉起支付,到最后的支付成功。这里我们定义 4 种简单的订单状态:创建订单、支付中、取消支付、支付成功。如下图所示:
我们针对上面的简图来用状态模式代码实现下业务
首先,我们来定义订单的状态 OrderState,在接口中声明一个更新状态的方法 updateState(),该方法接收订单上下文信息类 OrderContext 作为参数。
//定义订单状态接口
public interface OrderState {
/**
* 定义了4种状态
* 1 - 创建订单
* 2 - 订单支付中
* 3 - 用户中途取消支付
* 4 - 支付成功
* @param ctx
*/
void updateState(OrderContext ctx);
}
然后我们再来详细定义上下文信息类 orderContext,其中包含一个当前状态 OrderState 和一个订单编号(orderNo)
public class OrderContext {
private OrderState currentState; //订单状态
private String orderNo; //订单编号
public OrderContext(OrderState currentState, String orderNo) {
this.currentState = currentState;
this.orderNo = orderNo;
//默认初始化创建订单
if(currentState == null) {
this.currentState = Acknowledged.instance();
}
}
public OrderState getCurrentState() {
return currentState;
}
public void setCurrentState(OrderState currentState) {
this.currentState = currentState;
}
public String getOrderNo() {
return orderNo;
}
public void setOrderNo(String orderNo) {
this.orderNo = orderNo;
}
//开始修改状态(当前对象携带的信息)
public void update() {
currentState.updateState(this);
}
}
接下来,我们依次定义具体的状态类:创建订单(CreateOrder)、订单支付(OrderProcessing)、取消支付(CancelOrder)、支付成功(OrderPaySuccess)。每一个类都会实现 updateState() 方法,同时使用单例模式模拟状态的唯一性。
//创建订单
public class CreateOrder implements OrderState {
//Singleton
private static CreateOrder instance = new CreateOrder();
private CreateOrder() {}
public static CreateOrder instance() {
return instance;
}
@Override
public void updateState(OrderContext ctx) {
System.out.println("->. -> 开始创建订单");
//创建完订单 将订单修改为订单支付中......
ctx.setCurrentState(OrderProcessing.instance());
}
}
//订单支付中
public class OrderProcessing implements OrderState {
//Singleton
private static OrderProcessing instance = new OrderProcessing();
private OrderProcessing() {}
public static OrderProcessing instance() {
return instance;
}
@Override
public void updateState(OrderContext ctx) {
System.out.println("-> -> 订单支付中");
//接着改为支付成功(不考虑用户中途取消)
ctx.setCurrentState(InTransition.instance());
}
}
//订单支付成功
public class OrderPaySuccess implements OrderState {
//Singleton
private static OrderPaySuccess instance = new OrderPaySuccess();
private OrderPaySuccess() {}
public static OrderPaySuccess instance() {
return instance;
}
@Override
public void updateState(OrderContext ctx) {
System.out.println("-> -> 用户支付成功 end");
}
}
最后,我们运行一个单元测试,通过执行上下文信息类的更新操作了变更状态。
public class Client {
public static void main(String[] args) {
OrderContext ctx = new OrderContext(null, "123456789");
ctx.update();
ctx.update();
}
}
//输出
->. -> 开始创建订单
-> -> 订单支付中
-> -> 用户支付成功 end
OK,到这里我们的代码部分就结束了。可能会有人问了,为什么使用状态模式呢,原因主要有以下两个:
-
第一个,降低耦合性。当要设计的业务具有复杂的状态变迁时,我们期望通过状态变化来快速进行变更操作,并降低代码耦合性。
-
第二个避免增加代码的复杂性,要不然就得写满屏的if eles
总结
写到这里忽然想到 还有个例子更加让你容易理解,就是线程池的五种状态:一个线程的生命周期里有五种状态,只有在获得当前状态后才能确定下一个状态。
有得必有舍,任何模式都是这样的,我们考虑下状态模式的缺点。这样以后有利于我们更好的优化代码:
-
造成很多零散类。 状态模式因为需要对每一个状态定义一个具体状态类,所以势必会增加系统类和对象的个数
-
状态切换关系越复杂,代码实现难度越高,如果业务及其复杂,又比较耗时,不建议此中模式,应采用异步形式。
-
不满足开闭原,如果要改逻辑,比如要动具体里面的业务代码。
OK,状态模式 今天我们就讲到这里了!
弦外之音
感谢你的阅读,如果你感觉学到了东西,麻烦您点赞,关注。
我已经将本章收录在专题里,点击下方专题,关注专栏,我会每天发表干货,本月我会持续输入设计模式。
加油! 我们下期再见!