一、什么是状态模式
定义(GOF) :允许一个对象在其内部状态改变时改变它的行为,对象看起来好像修改了它的类。
一句话理解:把"对象在不同状态下该做什么"封装成一组状态类 ,对象持有一个当前状态引用;状态一变,同样的方法调用就表现出不同行为------而且状态怎么转移、何时转移由状态自己决定,客户端不感知。
二、为什么需要状态模式(动机)
假设一个人一天的行为随"时间"变化:7 点吃早餐、12 点吃中餐、18 点吃晚餐。
不用状态模式(巨型 if-else):
java
if (hour == 7) System.out.println("吃早餐");
else if (hour == 12) System.out.println("吃中餐");
else if (hour == 18) System.out.println("吃晚餐");
痛点:① 状态判断逻辑集中在一个超大条件表达式 里,随状态增多迅速膨胀、难以维护;② 新增状态要改这个方法,违反开闭原则;③ 行为与状态耦合在同一处。
用状态模式 :把每个状态(早晨/中午/傍晚)封装成 State 类,判断逻辑分散到各状态类里,状态之间自己决定"下一步去哪"。条件表达式消失,新增状态只需加类。
三、状态模式的结构
角色和职责
| 角色 | 职责 |
|---|---|
| Context(上下文) | 用户对象,持有当前 State 引用,对外提供统一方法(如 doSomething()),内部委托给当前状态 |
| State(抽象状态) | 封装与 Context 特定状态相关的行为(接口或抽象类) |
| ConcreteState(具体状态) | 实现某个状态下的行为,并负责状态转移(必要时把 Context 切到下一个状态) |
类结构图(mermaid)
#mermaid-svg-ArhEFBtokkJlIPKV{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ArhEFBtokkJlIPKV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ArhEFBtokkJlIPKV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ArhEFBtokkJlIPKV .error-icon{fill:#552222;}#mermaid-svg-ArhEFBtokkJlIPKV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ArhEFBtokkJlIPKV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ArhEFBtokkJlIPKV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ArhEFBtokkJlIPKV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ArhEFBtokkJlIPKV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ArhEFBtokkJlIPKV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ArhEFBtokkJlIPKV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ArhEFBtokkJlIPKV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ArhEFBtokkJlIPKV .marker.cross{stroke:#333333;}#mermaid-svg-ArhEFBtokkJlIPKV svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ArhEFBtokkJlIPKV p{margin:0;}#mermaid-svg-ArhEFBtokkJlIPKV g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-ArhEFBtokkJlIPKV g.classGroup text .title{font-weight:bolder;}#mermaid-svg-ArhEFBtokkJlIPKV .cluster-label text{fill:#333;}#mermaid-svg-ArhEFBtokkJlIPKV .cluster-label span{color:#333;}#mermaid-svg-ArhEFBtokkJlIPKV .cluster-label span p{background-color:transparent;}#mermaid-svg-ArhEFBtokkJlIPKV .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ArhEFBtokkJlIPKV .cluster text{fill:#333;}#mermaid-svg-ArhEFBtokkJlIPKV .cluster span{color:#333;}#mermaid-svg-ArhEFBtokkJlIPKV .nodeLabel,#mermaid-svg-ArhEFBtokkJlIPKV .edgeLabel{color:#131300;}#mermaid-svg-ArhEFBtokkJlIPKV .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-ArhEFBtokkJlIPKV .label text{fill:#131300;}#mermaid-svg-ArhEFBtokkJlIPKV .labelBkg{background:#ECECFF;}#mermaid-svg-ArhEFBtokkJlIPKV .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-ArhEFBtokkJlIPKV .classTitle{font-weight:bolder;}#mermaid-svg-ArhEFBtokkJlIPKV .node rect,#mermaid-svg-ArhEFBtokkJlIPKV .node circle,#mermaid-svg-ArhEFBtokkJlIPKV .node ellipse,#mermaid-svg-ArhEFBtokkJlIPKV .node polygon,#mermaid-svg-ArhEFBtokkJlIPKV .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ArhEFBtokkJlIPKV .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-ArhEFBtokkJlIPKV g.clickable{cursor:pointer;}#mermaid-svg-ArhEFBtokkJlIPKV g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-ArhEFBtokkJlIPKV g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-ArhEFBtokkJlIPKV .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-ArhEFBtokkJlIPKV .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-ArhEFBtokkJlIPKV .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-ArhEFBtokkJlIPKV .dashed-line{stroke-dasharray:3;}#mermaid-svg-ArhEFBtokkJlIPKV .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-ArhEFBtokkJlIPKV #compositionStart,#mermaid-svg-ArhEFBtokkJlIPKV .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ArhEFBtokkJlIPKV #compositionEnd,#mermaid-svg-ArhEFBtokkJlIPKV .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ArhEFBtokkJlIPKV #dependencyStart,#mermaid-svg-ArhEFBtokkJlIPKV .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ArhEFBtokkJlIPKV #dependencyStart,#mermaid-svg-ArhEFBtokkJlIPKV .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ArhEFBtokkJlIPKV #extensionStart,#mermaid-svg-ArhEFBtokkJlIPKV .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ArhEFBtokkJlIPKV #extensionEnd,#mermaid-svg-ArhEFBtokkJlIPKV .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ArhEFBtokkJlIPKV #aggregationStart,#mermaid-svg-ArhEFBtokkJlIPKV .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ArhEFBtokkJlIPKV #aggregationEnd,#mermaid-svg-ArhEFBtokkJlIPKV .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ArhEFBtokkJlIPKV #lollipopStart,#mermaid-svg-ArhEFBtokkJlIPKV .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ArhEFBtokkJlIPKV #lollipopEnd,#mermaid-svg-ArhEFBtokkJlIPKV .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ArhEFBtokkJlIPKV .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-ArhEFBtokkJlIPKV .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ArhEFBtokkJlIPKV .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ArhEFBtokkJlIPKV .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ArhEFBtokkJlIPKV :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 持有当前状态
转移
转移
<<abstract>>
State
+doSomething(Person)
MState
LState
DState
Person
-int hour
-State state
+doSomething()
+setState(State)
+getHour() : int
四、代码举例
java
// ========== 1. 上下文:持有当前状态 ==========
public class Person {
private int hour;
private State state;
public Person() {
state = new MState(); // 初始状态:早晨
}
public int getHour() { return hour; }
public void setHour(int hour) { this.hour = hour; }
public State getState() { return state; }
public void setState(State state) { this.state = state; }
// 纯委托:把行为交给当前状态,自己不写任何 if-else
public void doSomething() {
state.doSomething(this);
}
}
// ========== 2. 抽象状态 ==========
public abstract class State {
public abstract void doSomething(Person person);
}
// ========== 3. 具体状态:早晨 ==========
public class MState extends State {
@Override
public void doSomething(Person person) {
if (person.getHour() == 7) {
System.out.println("吃早餐");
} else {
// 不匹配就转移,并把请求交给下一个状态
person.setState(new LState());
person.doSomething();
}
}
}
// ========== 3. 具体状态:中午 ==========
public class LState extends State {
@Override
public void doSomething(Person person) {
if (person.getHour() == 12) {
System.out.println("吃中餐");
} else {
person.setState(new DState());
person.doSomething();
}
}
}
// ========== 3. 具体状态:傍晚(终态) ==========
public class DState extends State {
@Override
public void doSomething(Person person) {
if (person.getHour() == 18) {
System.out.println("吃晚餐");
} else {
System.out.println("时间未定义");
}
}
}
// ========== 4. 测试 ==========
public class MainClass {
public static void main(String[] args) {
Person person = new Person();
person.setHour(7);
person.doSomething(); // 吃早餐
person.setHour(12);
person.doSomething(); // 吃中餐
person.setHour(18);
person.doSomething(); // 吃晚餐
person.setHour(20);
person.doSomething(); // 时间未定义
}
}
运行结果
吃早餐
吃中餐
吃晚餐
时间未定义
注意:上面的"链式匹配"示例适合演示"从初始状态找匹配行为"的场景。它的状态转移是沿链单向 的(MState→LState→DState)。若你的业务需要状态长期驻留(如订单今天付了款、明天才发货),应采用下面的"状态驻留写法"。
五、进阶:更地道的状态驻留写法(订单状态机)
真正工程里,状态模式的价值是状态长期驻留 + 由事件触发转移 。以一个订单为例:新建 → 已支付 → 已发货 → 已完成,每个状态下能做的操作不同,且状态由 pay()/ship() 等事件驱动转移:
java
// 抽象状态:每个事件方法默认拒绝,由具体状态重写
public interface OrderState {
void pay(Order order);
void ship(Order order);
String name();
}
// 新建状态:能支付,支付后转"已支付"
public class NewState implements OrderState {
public void pay(Order o) { System.out.println("支付成功"); o.setState(new PaidState()); }
public void ship(Order o) { System.out.println("错误:未支付不能发货"); }
public String name() { return "NEW"; }
}
// 已支付状态:能发货,发货后转"已发货"
public class PaidState implements OrderState {
public void pay(Order o) { System.out.println("已支付,无需重复支付"); }
public void ship(Order o) { System.out.println("发货成功"); o.setState(new ShippedState()); }
public String name() { return "PAID"; }
}
// 已发货状态:支付/发货都不再响应(或转完成)
public class ShippedState implements OrderState {
public void pay(Order o) { System.out.println("已发货,无需支付"); }
public void ship(Order o) { System.out.println("已发货,无需重复发货"); }
public String name() { return "SHIPPED"; }
}
public class Order {
private OrderState state = new NewState();
public void setState(OrderState s) { this.state = s; }
public void pay() { state.pay(this); }
public void ship() { state.ship(this); }
public String state() { return state.name(); }
}
java
Order o = new Order();
o.pay(); // 支付成功(NEW→PAID)
o.ship(); // 发货成功(PAID→SHIPPED)
o.pay(); // 已发货,无需支付
这种写法里:状态会驻留 (payment 后停在 PAID),转移由状态自己决定 (NewState 里 o.setState(new PaidState())),客户端只调用 pay()/ship() 业务事件,完全不感知当前是哪个状态------这才是状态模式最典型的应用形态。
六、状态模式 vs 策略模式(本质区别)
上篇《策略模式》提过二者结构极像(都持有对象、都委托调用),但意图完全不同:
| 维度 | 状态模式 | 策略模式 |
|---|---|---|
| 谁决定用哪个 | 对象内部状态自动转移,客户端不感知"在换算法" | 客户端显式传入/set 一个策略 |
| 客户端是否感知切换 | 不感知(只知道调业务方法) | 感知(自己选了策略) |
| 转移发生在哪 | 状态类内部 setState(...) 自包含 |
由客户端/Context 外部决定 |
| 核心意图 | 同一对象随状态自动变行为 | 同一件事换不同做法 |
| 状态是否驻留 | 是(状态长期存在) | 否(算法可随时换,无所谓"驻留") |
记忆口诀:策略是"我(客户端)选打法",状态是"我(对象)随处境变打法"。
七、真实世界中的状态模式(JDK / 框架)
java.lang.Thread.State:NEW / RUNNABLE / BLOCKED / WAITING / TIMED_WAITING / TERMINATED。线程对象的行为(能否运行、是否阻塞)随状态而变------状态模式概念的最直观体现(虽其内部未必用 State 类实现,但是该模式的教科书级案例)。- Servlet 生命周期 :
init → service → destroy,以及HttpSession的创建/活跃/过期状态。 - 订单 / 工单状态机:待支付→已支付→已发货→已完成;待处理→处理中→已关闭。业务里无处不在。
- Spring Statemachine / Activiti 工作流:用专门的引擎管理状态与转移,是状态模式的企业级落地。
- TCP 连接状态:CLOSED / LISTEN / ESTABLISHED / TIME_WAIT 等,连接对象行为随状态切换。
八、应用场景与优缺点
适用场景
- 对象行为依赖它的状态 ,且运行时状态会频繁变化。
- 存在大量与状态相关的 if-else / switch 分支,且随状态增多难以维护。
- 状态转移规则清晰,希望把"判断逻辑"分散到各状态类里。
优点
- 消除巨型条件分支 :状态相关行为分散到各
ConcreteState,代码清爽。 - 符合开闭原则 :新增状态只需加一个
ConcreteState类,改一处转移即可。 - 状态转移显式化 :用类与
setState把"状态机"表达得比 if-else 清晰得多,也便于加日志/校验。
缺点
- 类数量增加 :每个状态一个类,状态多时类膨胀(可结合享元共享无状态实例)。
- 状态转移分散 :转移散落在各状态类里,全局"状态图"不如一张集中表直观(复杂场景建议用专门的状态机引擎统一管理)。
九、总结
一句话 :状态模式 = 把对象在不同状态下的行为封装成一组状态类,状态自己决定"做什么 + 转去哪",让对象随内部状态自动改变行为。
记忆口诀:
条件分支太臃肿,拆成状态各管各;
行为随境自己变,转移自包含其中。