设计模式-状态模式
前言
在 Java 开发中,设计模式是常用的一种编码方式。合理使用设计模式,可以帮助开发人员更快地写出可靠、容易维护的代码。本期继续更新设计模式系列,总共 23 种设计模式会用一篇一篇文章来讲解,相关代码已经开源至:Gitee点击跳转
上一篇《Java设计模式(18)观察者模式》讲了对象之间的订阅和通知。本文是这个系列的第十九篇,继续介绍行为型设计模式中的状态模式,主要看它解决什么问题,以及如何用它处理订单状态的流转。
状态模式
状态模式是一种行为型设计模式。当一个对象的内部状态发生变化时,它的行为也会跟着改变。对外看起来,就像这个对象换了一套处理方式。
平时写业务代码,经常会遇到这种判断:订单是新建状态时执行一段逻辑,处理中执行另一段逻辑,完成后又有不同的处理。最直接的写法是在一个方法里堆叠 if、else if 或 switch。状态少的时候问题不大,但状态一多,判断会越来越长。新增状态时,还要继续修改原来的方法。
状态模式的做法是把每一种状态的行为拆到单独的类中,再让业务对象保存当前状态。业务对象收到请求后,不自己判断应该执行哪一段逻辑,而是交给当前状态对象处理。
使用场景
- 一个对象会随着状态变化表现出不同行为,例如订单、工单、审批单和售后单。
- 业务中有较多状态判断,并且这些判断会继续增加。
- 状态之间有明确的流转关系,例如新建、处理中、已完成。
- 不同状态下允许执行的操作不同,例如未支付订单可以取消,已发货订单只能申请售后。
代码实现
下面直接使用项目中的订单示例。一个订单创建后默认处于新建状态,每调用一次 processOrder 方法,当前状态就负责处理订单,并决定是否进入下一个状态。
首先定义订单状态接口。所有具体状态都实现同一个 processOrder 方法,这样 Order 不需要依赖某个具体状态的处理细节。
/**
* 订单状态接口
* @author Jensen
* @date 2024-01-19
*/
public interface OrderState {
void processOrder(Order order);
}接着定义订单对象。Order 保存当前的 OrderState,创建订单时默认使用 NewOrder。调用 processOrder 后,订单会把自己传给当前状态对象。
/**
* 订单对象
* @author Jensen
* @date 2024-01-19
*/
public class Order {
private OrderState orderState;
public Order() {
orderState = new NewOrder();
}
public void setOrderState(OrderState orderState) {
this.orderState = orderState;
}
public void processOrder() {
orderState.processOrder(this);
}
}新订单状态负责处理刚创建的订单。当前逻辑执行完成后,它把订单状态修改为 ProcessingOrder。
/**
* 新订单
* @author Jensen
* @date 2024-01-19
*/
public class NewOrder implements OrderState {
@Override
public void processOrder(Order order) {
System.out.println("新建订单");
order.setOrderState(new ProcessingOrder());
}
}ProcessingOrder 表示订单正在处理中。处理完成后,它会把订单切换到 CompleteOrder。
/**
* 处理中的订单
* @author Jensen
* @date 2024-01-19
*/
public class ProcessingOrder implements OrderState {
@Override
public void processOrder(Order order) {
System.out.println("订单处理中");
order.setOrderState(new CompleteOrder());
}
}CompleteOrder 是这个示例的最后一个状态。订单已经完成,所以这里只输出处理结果,不再切换到其他状态。
/**
* 订单已完成
* @author Jensen
* @date 2024-01-19
*/
public class CompleteOrder implements OrderState {
@Override
public void processOrder(Order order) {
System.out.println("订单已完成");
}
}解释
- 订单状态接口(OrderState):约定所有状态都要实现 processOrder 方法。调用方只依赖这个接口,不需要知道当前状态类的具体类型。
- 订单类(Order):它是状态模式中的上下文对象,也就是 Context。Order 保存当前状态,并把订单处理请求交给当前状态对象。setOrderState 方法用于完成状态切换。
- 新订单状态(NewOrder):负责新订单阶段的处理。执行完成后,将订单状态设置为 ProcessingOrder。
- 处理中状态(ProcessingOrder):负责订单处理阶段的逻辑。执行完成后,将订单状态设置为 CompleteOrder。
- 完成状态(CompleteOrder):负责完成状态下的行为。在这个例子中,它是终止状态,不再继续流转。
这套代码里没有根据状态值编写 if 或 switch。Order 每次调用的都是同一个 processOrder 方法,但真正执行的代码取决于当前保存的是哪个 OrderState。状态切换也放在具体状态类中,因此每个状态既知道自己要做什么,也知道处理完成后应该走向哪里。
测试
System.out.println("------------------------------状态模式-----------------------------");
Order order = new Order();
order.processOrder(); // 新建订单
order.processOrder(); // 订单处理中
order.processOrder(); // 订单已完成输出结果
------------------------------状态模式-----------------------------
新建订单
订单处理中
订单已完成从输出可以看到,同一个 Order 对象连续调用三次 processOrder,得到的结果并不相同。第一次调用时,订单内部保存的是 NewOrder;处理后切换为 ProcessingOrder。第二次调用又切换为 CompleteOrder,第三次则执行完成状态的逻辑。
状态模式的优缺点
状态模式把不同状态的处理逻辑分开保存,单个类的职责比较清楚。新增状态时,通常只需要增加一个 OrderState 实现类;修改某个状态的行为,也不会把其他状态的代码一起带进来。
它的代价也很直接:状态越多,类的数量越多。状态之间的流转如果比较复杂,转换关系会分散在多个状态类中,排查流程时需要在这些类之间来回查看。所以状态很少、逻辑也不会变化时,用简单判断反而更省事。
状态模式和策略模式看起来有些像,它们都是通过接口切换具体实现。区别在于,策略通常由外部主动选择,用来替换某种算法;状态则由对象当前所处阶段决定,状态对象往往还会推动下一次状态变化。下一篇讲策略模式时,可以再对照着看
。
结语
状态模式把一个对象在不同状态下的行为拆分到多个状态类中,再由上下文对象把请求交给当前状态处理。订单对象不再维护一长串状态判断,状态变化后,后续调用自然会走到新的处理逻辑。
实际开发中的订单、审批、工单和任务流程通常都有明确的状态变化。状态数量较多,而且每个状态都有独立行为时,状态模式会比不断扩展 if 和 switch 更容易维护。不过也不必为了使用设计模式而使用设计模式,状态只有两三个、逻辑很简单时,清楚的条件判断已经足够。![[太赞了]](https://www.oxoxtech.com/frontSystem/emoji/太赞了.png#emoji)