一 概述
状态模式在日常开发中是一个非常实用的模式,可以将你的代码逼格迅速提升一个档次,所以让我们开始今天的卓越之旅吧。
1.1 定义
当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。
- 状态模式属于行为型模式
- 状态模式中的行为是由状态来决定的,即不同状态下的行为也不同
状态模式的结构跟策略模式的几乎一样,但其本质是不一样的。策略模式中的行为是彼此独立,能够相互替换的;而状态模式的行为是平行的,不同状态下其行为也是不一样的,具有不可替换性。
1.2 使用场景
当你发现你的代码里面存在一个很长的 if else 列表,而这些分支都是因为不同状态下执行的操作不一样时考虑使用此模式。
1.3 UML 类图
角色说明:
- State(抽象状态角色):抽象类或者接口,定义对象的各种状态和行为
- ConcreteState(具体状态角色):State 的实现类,表示具体的状态
- Context(环境角色):保持并切换各个状态,其持有一个 State 的引用。它将依赖状态的各种操作委托给不同的状态对象执行。其负责与客户端交互
二 实现
最近王二狗又要过生日了,近两年他内心中是非常抗拒过生日的,因为每过一个生日就意味着自己又老一岁,离被辞退的35岁魔咒又近了一步。可惜时间是不以人的意志为转移的,任何人都阻止不了时间的流逝,所以该过还的过。令二狗比较欣慰的时,这次过生日,老婆送了他一个自己一直想要的机械键盘作为生日礼物… 翠花于是在二狗生日前3天在京东上下了一个单。。。
自从下单以来,二狗天天看物流状态信息,心心念念着自己的机械键盘快点到。。。
这个物流系统就很适合使用状态模式来开发,因为此过程存在很多不同的状态,例如接单,出库,运输,送货,收货,评价等等。而订单在每个不同的状态下的操作可能都不一样,例如在接单状态下,商家就需要通知仓库拣货,通知用户等等操作,其他状态类似
下面是实例的 UML 类图:
2.1 定义一个状态接口
此接口定义各个状态的统一操作接口。
public interface LogisticsState {
void doAction(JdLogistics context);
}
2.2 定义一个物流 Context 类
此类持有一个 LogisticsState 的引用,负责在流程中保持并切换状态。
public class JdLogistics {
private LogisticsState logisticsState;
public void setLogisticsState(LogisticsState logisticsState) {
this.logisticsState = logisticsState;
}
public LogisticsState getLogisticsState() {
return logisticsState;
}
public void doAction(){
Objects.requireNonNull(logisticsState);
logisticsState.doAction(this);
}
}
2.3 实现各种状态类
1、接单状态类,其需要实现 LogisticsState 接口
public class OrderState implements LogisticsState {
@Override
public void doAction(JdLogistics context) {
System.out.println("商家已经接单,正在处理中...");
}
}
2、出库状态类
public class ProductOutState implements LogisticsState {
@Override
public void doAction(JdLogistics context) {
System.out.println("商品已经出库...");
}
}
依次类推,可以建立任意多个状态类。
2.4 客户端使用
public class StateClient {
public void buyKeyboard() {
//状态的保持与切换者
JdLogistics jdLogistics = new JdLogistics();
//接单状态
OrderState orderState = new OrderState();
jdLogistics.setLogisticsState(orderState);
jdLogistics.doAction();
//出库状态
ProductOutState productOutState = new ProductOutState();
jdLogistics.setLogisticsState(productOutState);
jdLogistics.doAction();
//运输状态
TransportState transportState = new TransportState();
jdLogistics.setLogisticsState(transportState);
jdLogistics.doAction();
}
}
输出结果:
商家已经接单,正在处理中...
商品已经出库...
商品正在运往天津分发中心
可见,我们将每个状态下要做的具体动作封装到了每个状态类中,我们只需要切换不同的状态即可。如果不使用状态模式,我们的代码中可能会出现很长的 if else 列表,这样就不便于扩展和修改了。
三 总结
3.1 特点
- 必须要有一个 Context 类,这个类持有 State 接口,负责保持并切换当前的状态
- 状态模式没有定义在哪里进行状态转换,本例是在 Context 类进行的,也有人在具体的 State 类中转换
- 当使用 Context 类切换状态时,状态类之间互相不认识,他们直接的依赖关系应该由客户端负责
例如,只有在接单状态的操作完成后才应该切换到出库状态,那么出库状态就对接单状态有了依赖,这个依赖顺序应该由客户端负责,而不是在状态内判断。 - 当使用具体的 State 类切换时,状态之间就可能互相认识,一个状态执行完就自动切换到了另一个状态去了
3.2 优点
- 增强了程序的可扩展性,因为我们很容易添加一个 State
- 增强了程序的封装性,每个状态的操作都被封装到了一个状态类中
- 避免过多的条件语句,使得结构更清晰,提高代码的可维护性。
3.3 缺点
可能会导致状态子类会过多。
3.4 状态模式与策略模式区别
状态模式与策略模式的 UML 类图都是一样的,从表面上看他们非常相似。特别是将状态切换任务放在 Context 中做的时候就更像了,但是其背后的思想却非常不同。
- 策略模式定义了一组可互相代替的算法,这一组算法对象完成的是同一个任务,只是使用的方式不同,例如同样是亿万富翁,马云通过卖东西实现,而王思聪通过继承实现
- 状态模式不同的状态完成的任务完全不一样