状态 State
状态模式是一种行为设计模式,让你能在一个对象的内部状态变化时改变其行为,使其看上去就像改变了自身所属的类一样。或者说通过控制状态的变化使其行为发生变化。
为什么要使用?
状态模式的对象职责:
通过控制状态的变化使其行为发生变化。找到合适的抽象状态以及状态之间的转移关系,通过改变状态来达到改变行为的目的。
当要设计的业务具有复杂的状态变迁时,期望通过状态变化来快速进行变更操作,并降低代码耦合性,避免增加代码的复杂性。
例如,一首歌曲的状态可以是“📝草稿”、“🔬审核中”、“📤上架”、“📥下架”。
- “草稿状态”只能跃迁到“审核状态”;
- “审核状态”如果审核成功可以跃迁到“上架状态”;如果审核失败则跃迁到“草稿状态”;
- “上架状态”和“下架状态”可以任意切换;
- “下架状态”可以选择永久下架,即一首歌曲的生命周期结束。
模式结构
上下文(Context)保存了一个具体的状态对象,并会将所有与该状态相关的工作委派给它。上下文通过状态接口与状态对象交互,且会向外界提供一个更新状态的操作。
状态(State)接口会声明特定于状态的方法,这些方法应该适用于每个具体状态。
具体状态(Concrete States)会自行实现特定于状态的方法。为了避免多个状态中包含相似代码,你可以提供一个封装有部分通用行为的中间抽象类。状态对象可存储对于上下文对象的反向引用。状态可以通过该引用从上下文处获取所需信息,并且能触发状态转移。上下文和具体状态都可以设置上下文的下个状态,并可通过替换连接到上下文的状态对象来完成实际的状态转换。
状态模式的类图:
模式实现
该示例使用状态模式把包裹的状态分为 6 个部分,分别为“已下单”、“仓库处理中”、“运输中”、“派送中”、“待取件”、“已签收”。每个状态按顺序更新。每次调用 PackageContext
的 update
方法时,它就会根据当前状态,去调用当前状态的 updateState
方法。每个状态的 updateState
方法中会设置下一个状态为当前状态,以便下次调用 PackageContext
的 update
方法时,能够调用新状态的 updateState
方法。
示例程序的类图
示例程序的状态图
代码实现
上下文
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package example;
public class PackageContext { private PackageState currentState; private String packageId;
public PackageContext(PackageState currentState, String packageId) { this.currentState = currentState; this.packageId = packageId; }
public void setCurrentState(PackageState currentState) { this.currentState = currentState; }
public void update() { System.out.print("当前包裹id:" + packageId + " --> 包裹状态:"); currentState.updateState(this); System.out.println(); } }
|
状态接口
1 2 3 4 5 6 7 8 9 10
| package example;
public interface PackageState {
void updateState(PackageContext ctx); }
|
具体状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package example;
public class Acknowledged implements PackageState { private static Acknowledged instance = new Acknowledged();
private Acknowledged() { }
public static Acknowledged getInstance() { return instance; }
@Override public void updateState(PackageContext ctx) { System.out.println("包裹已下单..."); ctx.setCurrentState(WarehouseProcessing.getInstance()); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package example;
public class WarehouseProcessing implements PackageState { private static WarehouseProcessing instance = new WarehouseProcessing();
private WarehouseProcessing() { }
public static WarehouseProcessing getInstance() { return instance; }
@Override public void updateState(PackageContext ctx) { System.out.println("包裹正在仓库处理中..."); ctx.setCurrentState(InTransition.getInstance()); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package example;
public class InTransition implements PackageState { private static InTransition instance = new InTransition();
private InTransition() { }
public static InTransition getInstance() { return instance; }
@Override public void updateState(PackageContext ctx) { System.out.println("包裹正在运输中..."); ctx.setCurrentState(Delivering.getInstance()); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package example;
public class Delivering implements PackageState { private static Delivering instance = new Delivering();
private Delivering() { }
public static Delivering getInstance() { return instance; }
@Override public void updateState(PackageContext ctx) { System.out.println("包裹已下单..."); ctx.setCurrentState(WaitForPickUp.getInstance()); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package example;
public class WaitForPickUp implements PackageState { private static WaitForPickUp instance = new WaitForPickUp();
private WaitForPickUp() { }
public static WaitForPickUp getInstance() { return instance; }
@Override public void updateState(PackageContext ctx) { System.out.println("包裹待取件..."); ctx.setCurrentState(Received.getInstance()); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package example;
public class Received implements PackageState { private static Received instance = new Received();
private Received() { }
public static Received getInstance() { return instance; }
@Override public void updateState(PackageContext ctx) { System.out.println("包裹已签收..."); } }
|
代码测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import example.Acknowledged; import example.PackageContext;
public class Test { public static void main(String[] args) { PackageContext context = new PackageContext(Acknowledged.getInstance(), "9527"); context.update(); context.update(); context.update(); context.update(); context.update(); context.update(); } }
|
输出结果
1 2 3 4 5 6 7 8 9 10 11
| 当前包裹id:9527 --> 包裹状态:包裹已下单...
当前包裹id:9527 --> 包裹状态:包裹正在仓库处理中...
当前包裹id:9527 --> 包裹状态:包裹正在运输中...
当前包裹id:9527 --> 包裹状态:包裹已下单...
当前包裹id:9527 --> 包裹状态:包裹待取件...
当前包裹id:9527 --> 包裹状态:包裹已签收...
|
常用场景和解决方案
- 对象根据自身状态的变化来进行不同行为的操作,同时状态的数量非常多且与状态相关的代码会频繁变更的话,可以使用状态模式。
- 对象需要根据自身变量的当前值改变行为,但不期望使用大量
if-else
语句时。
- 对于某些确定的状态和行为,不想使用重复代码时。
模式的优缺点
优点 |
缺点 |
单一职责原则。将与特定状态相关的代码放在单独的类中。 |
如果状态机只有很少的几个状态,或者很少发生改变, 那么应用该模式可能会显得小题大作。 |
开闭原则。无需修改已有状态类和上下文就能引入新状态。 |
如果要修改当前状态类,就有可能要修改前一个状态类和后一个状态类。 |
通过消除臃肿的状态机条件语句简化上下文代码。 |
|
使用状态模式的优势
- 提前定好可能的状态,降低代码实现复杂度。
- 快速理解状态和行为之间的关系。
- 避免写大量的
if-else
条件语句。
- 可以让多个环境对象共享一个状态对象,从而减少重复代码。
使用状态模式的劣势
- 造成很多零散类。
- 状态切换关系越复杂,代码实现难度越高。
拓展知识
- 状态可被视为策略的扩展。两者都基于组合机制:它们都通过将部分工作委派给“帮手”对象来改变其在不同情景下的行为。策略使得这些对象相互之间完全独立,它们不知道其他对象的存在。但状态模式没有限制具体状态之间的依赖,且允许它们自行改变在不同情景下的状态。
- 状态模式类似于面向对象的“关注点分离”,即将问题分解,让每个对象专注于每个问题。状态模式将一个对象的每种状态分离,使每个状态对象只专注于当前状态。而至于状态间的转换则交给上下文去处理。
🔙 设计模式
📌最后:希望本文能够给您提供帮助,文章中有不懂或不正确的地方,请在下方评论区💬留言!
🔗参考文献:
🌐 设计模式 –refactoringguru
▶️ bilibili-趣学设计模式;黄靖锋. –拉勾教育
📖 图解设计模式 /(日)结城浩著;杨文轩译. –北京:人民邮电出版社,2017.1