今天要跟大家聊的是状态模式(State Pattern),它描述的是程序因为内部状态发生转变而有不同行为的情况。比如说自动售货机,当用户把钱塞入投币处,对于自动售货机来说,它的投币状态变成了已投币,于是它的行为也发生了变化——将货物移动到取货处。完成后,自动售货机的投币状态又转移回了未投币,于是它的行为又发生了变化——不出货。
1. 定义
状态模式是一种行为型设计模式,它的定义是:“允许对象在其内部状态发生改变时改变其行为,这个对象看起来像改变了其类。”
这里需要注意的是,对象会因为自身内部状态的变动而改变自身行为,这看起来跟策略模式有点像,只不过在策略模式中,客户端通过传入具体的策略类来执行不同的业务逻辑。而状态模式中,对象的状态不是维护在具体状态类中而是在上下文角色中的,同时在执行过程中上下文角色会调用具体状态类的状态转移方法改变对象的行为
2. 类图
在状态模式中,有这样几种角色:
- 环境角色(Context):定义客户端需要的接口,并且负责具体状态的切换
- 抽象状态角色(State):通常是接口或抽象类,负责对象状态定义,并且封装环境角色用以实现状态切换
- 具体状态角色(ConcreteState):抽象状态角色的实现类,每个具体状态必须完成两个职责:当前状态需要做的事以及如何转移到其他状态
状态模式的类图如下:
3. 示例
今天就以电商系统中订单支付业务来聊一聊状态模式的应用。首先来说一下缩减后的简单业务:
- 当用户选购商品后确认订单,订单状态变为待支付
- 当用户支付成功后,订单状态变为待发货
- 当商家发货后,订单状态变为待收货
- 当用户收货后,订单状态变为已完成
当然在业务描述中没有涉及异常场景,这里就不做讨论了。
首先定义订单状态抽象类,它持有上下文角色(订单上下文)同时描述了该状态下订单的执行逻辑(包含订单状态转移逻辑),其代码如下:
public abstract class OrderState {
private OrderContext context;
// 订单在指定状态下,系统执行的业务
public abstract void handle();
// 省略 setter/getter 方法
}
然后定义待支付、待发货、待收货这三个订单状态,其代码如下:
public class UnPaidState extends OrderState {
@Override
public void handle() {
System.out.println("当前订单状态为[待支付]");
System.out.println("用户支付成功");
super.getContext().setState(new UnDeliverState());
System.out.println("当前订单状态为[待发货]");
}
}
public class UnDeliverState extends OrderState {
@Override
public void handle() {
System.out.println("当前订单状态为[待发货]");
System.out.println("商家发货成功");
super.getContext().setState(new UnReceiveState());
System.out.println("当前订单状态为[待收货]");
}
}
public class UnReceiveState extends OrderState {
@Override
public void handle() {
System.out.println("当前订单状态为[待收货]");
System.out.println("用户确认收货");
System.out.println("当前订单状态为[已完成]");
}
}
然后定义订单上下文类,其代码如下:
public class OrderContext {
private OrderState state;
public void setState(OrderState state) {
this.state = state;
this.state.setContext(this);
}
public void handle() {
// 执行该状态下的业务逻辑
this.state.handle();
}
}
然后编写测试代码:
public class Test {
public static void main(String[] args) {
OrderContext context = new OrderContext();
context.setState(new UnPaidState());
System.out.println("-----用户确认订单-----");
context.handle();
System.out.println("-----商家发货-----");
context.handle();
System.out.println("-----用户确认收货-----");
context.handle();
}
}
输出结果为:
-----用户确认订单-----
当前订单状态为[待支付]
用户支付成功
当前订单状态为[待发货]
-----商家发货-----
当前订单状态为[待发货]
商家发货成功
当前订单状态为[待收货]
-----用户确认收货-----
当前订单状态为[待收货]
用户确认收货
当前订单状态为[已完成]
4. 使用场景
状态模式的使用场景是:
- 行为随状态改变而改变
- 如果代码中存在大量条件、分支判断语句,可以考虑使用状态模式重构
5. 小结
本文讲述了状态模式,它的定义是——允许对象在其内部状态发生改变时改变其行为,这个对象看起来像改变了其类。
状态模式的优点是:
- 避免程序的复杂性,提高系统的可维护性:能够避免大量条件、分支判断语句
- 符合单一职责原则,扩展性、封装性很好
- 每个状态都由一个具体状态类实现
- 如果需要新增状态只需要新增具体状态类
- 如果需要修改状态只需要修改对应的子类即可
状态模式的缺点是:
- 如果状态非常多的话,可能导致存在大量具体状态类,影响代码可读性
- 不符合开闭原则,状态转移逻辑是定义在具体状态类中的,若需要修改大量源码
6. 参考资料
- 《设计模式之禅》(第2版)
- 状态模式(详解版)
最后,本文收录于个人语雀知识库: 我所理解的后端技术,欢迎来访。
文档信息
- 本文作者:Planeswalker23
- 本文链接:https://planeswalker23.github.io/2022/02/07/%E6%88%91%E6%89%80%E7%90%86%E8%A7%A3%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%B3%BB%E5%88%97-%E7%AC%AC16%E7%AF%87-%E7%94%B5%E5%95%86%E7%B3%BB%E7%BB%9F%E5%9F%BA%E4%BA%8E%E7%8A%B6%E6%80%81%E6%A8%A1%E5%BC%8F%E5%AE%9E%E7%8E%B0%E8%AE%A2%E5%8D%95%E6%94%AF%E4%BB%98%E4%B8%9A%E5%8A%A1/
- 版权声明:本作品系原创,作者保留所有权利,未经作者允许,禁止转载和演绎。