系列定位:软考软件设计师 / 系统架构设计师 --- 行为型模式专题第 2 讲
考察分值:上午题 1-2 分,下午题常作为代码填空或类图识别出现
难度等级:⭐⭐⭐⭐☆(与策略模式极易混淆,状态自动转换是核心判分点)
一、考纲定位与模式定义
1.1 考纲要求
状态模式在软考中属于 行为型模式 的中高难度内容。考察形式包括:
-
上午选择题:判断场景描述所属模式;识别状态模式与策略模式的本质区别(自动转换 vs 客户端选择);判断类图中 State 与 Context 的双向关联关系;识别状态转换逻辑的位置
-
下午设计题 :补全 Context 中
setState()方法的调用逻辑;补全 ConcreteState 中handle()方法内根据条件转换到下一个状态的代码;识别类图中 Context、State、ConcreteState 三个角色及状态转换箭头
1.2 模式定义
状态模式:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
核心意图 :当一个对象的行为取决于它的状态,且状态改变时需要改变行为,同时状态转换有固定的规则(如 "待支付 → 已支付 → 已发货 → 已签收"),如果将所有状态的行为和转换逻辑都写在一个类里,会导致类臃肿、充斥着大量的 if-else 或 switch-case。状态模式将 每种状态封装为独立的类 ,状态对象自己管理 状态转换规则,Context 只需将请求委托给当前状态对象处理。当状态改变时,Context 的行为看起来就像换了一个类。
通俗理解:
电梯有多个状态:关门、开门、运行、停止。每个状态下按同一个按钮(如 "开门按钮"),行为完全不同:
-
关门状态 → 按开门按钮 → 门打开
-
开门状态 → 按开门按钮 → 无反应(已经是开着的)
-
运行状态 → 按开门按钮 → 报警(运行中不能开门)
-
停止状态 → 按开门按钮 → 门打开
如果把这些逻辑都写在 Elevator 类里,就是一堆 if (state == CLOSED) { ... } else if (state == RUNNING) { ... }。状态模式的思路是:把每种状态变成一个独立的类(ClosedState、OpenState、RunningState、StoppedState),每个状态类自己实现 "按开门按钮" 的行为,并且自己决定处理完事件后是否要转换到下一个状态(如关门状态处理完开门请求后,自动转换到开门状态)。电梯(Context)只需要把请求转发给当前状态对象,完全不需要知道状态转换的细节。
与策略模式的关键区别 :策略模式是 客户端主动选择 算法(
context.setStrategy(new XxxStrategy()));状态模式是 状态对象内部自动转换 (context.setState(new NextState())发生在 State 的方法内部),客户端通常只触发事件,不参与状态选择。
二、UML 类图与角色划分
┌─────────────────────────────────────────────────────────┐
│ Client │
│ + main() │
│ ├── Context ctx = new Context(); │
│ ├── ctx.setState(new ConcreteStateA(ctx)); │
│ └── ctx.request(); // 触发事件,由当前状态处理并可能转换 │
└───────────────────────┬─────────────────────────────────┘
│ uses
▼
┌─────────────────────────────────────────────────────────┐
│ Context │
│ - state: State ◄── 组合当前状态 │
│ + setState(State s) { │
│ this.state = s; │
│ System.out.println("状态切换为: " + s.getName()); │
│ } │
│ + request() { │
│ state.handle(); // 委托给当前状态处理 │
│ } │
└───────────────────────┬─────────────────────────────────┘
▲ │ delegates to
│ ▼
│ ┌─────────────────────────────────────────┐
│ │ <<interface>> │
│ │ State │
│ │ + handle() │
│ │ + getName(): String │
│ └───────────────────────┬─────────────────┘
│ ┌────────────┴────────────┐
│ ▼ ▼
│ ┌───────────────────────┐ ┌───────────────────────┐
│ │ ConcreteStateA │ │ ConcreteStateB │
│ │ - context: Context │ │ - context: Context │
│ │ + handle() { │ │ + handle() { │
│ │ // 处理事件 │ │ // 处理事件 │
│ │ // 条件满足时 │ │ // 条件满足时 │
│ │ // 自动转换到 B │ │ // 自动转换到 A │
│ │ context.setState │ │ context.setState │
│ │ (new StateB │ │ (new StateA │
│ │ (context)); │ │ (context)); │
│ │ } │ │ } │
│ └───────────────────────┘ └───────────────────────┘
│
└── Context 的 setState() 被 State 调用,实现双向关联
| 角色 | 职责 | 软考填空关键词 |
|---|---|---|
| Context(上下文) | 定义客户端感兴趣的接口,维护一个 ConcreteState 的实例(当前状态),将状态相关的请求委托给当前状态对象处理 | class + private State state + setState() + request() |
| State(抽象状态) | 定义一个接口,封装与 Context 的一个特定状态相关的行为 | interface / abstract class + handle() + getName() |
| ConcreteState(具体状态) | 实现 State 接口,封装 Context 在 该状态下 的行为;包含状态转换逻辑 (根据条件调用 context.setState(new NextState(context))) |
implements State + handle() 内调用 context.setState() |
| Client(客户端) | 创建 Context 和初始状态,触发事件(调用 context.request()),不直接参与状态转换 |
new Context() + context.setState(new InitialState(context)) + context.request() |
类图识别要点 :软考类图中,状态模式的核心特征是 Context 与 State 是双向关联 (Context 持有 State,State 持有 Context 引用用于状态转换)。如果类图里 State 的方法内部调用了
context.setState()→ 状态模式。如果 State 只是被客户端通过setStrategy()注入 → 策略模式。
三、场景一:订单状态机(最经典的状态模式)
业务背景:电商订单有固定的生命周期:待支付(Pending)→ 已支付(Paid)→ 已发货(Shipped)→ 已签收(Delivered)。每个状态下触发同一个事件(如 "支付"、"发货"、"确认收货"),行为不同,且处理完事件后会自动转换到下一个状态。使用状态模式,将每种订单状态封装为独立类,状态类自己管理转换规则。
说明:这是状态模式最经典的例子,也是软考下午题最爱考的 "状态机" 场景。关键是 状态转换逻辑写在 State 内部。
3.1 代码实现
java
// State:订单状态接口
public interface OrderState {
String getName();
void pay();
void ship();
void confirm();
}
// Context:订单上下文
public class OrderContext {
private OrderState state;
private String orderId;
public OrderContext(String orderId) {
this.orderId = orderId;
this.state = new PendingState(this);
}
public void setState(OrderState state) {
this.state = state;
System.out.println("订单 " + orderId + " 状态切换为: " + state.getName());
}
public void pay() { state.pay(); }
public void ship() { state.ship(); }
public void confirm() { state.confirm(); }
public String getOrderId() { return orderId; }
}
// ConcreteState:待支付状态
public class PendingState implements OrderState {
private OrderContext context;
public PendingState(OrderContext context) {
this.context = context;
}
@Override
public String getName() { return "待支付"; }
@Override
public void pay() {
System.out.println("[待支付] 订单 " + context.getOrderId() + " 支付成功!");
context.setState(new PaidState(context));
}
@Override
public void ship() {
System.out.println("[待支付] 订单未支付,不能发货!");
}
@Override
public void confirm() {
System.out.println("[待支付] 订单未支付,不能确认收货!");
}
}
// ConcreteState:已支付状态
public class PaidState implements OrderState {
private OrderContext context;
public PaidState(OrderContext context) {
this.context = context;
}
@Override
public String getName() { return "已支付"; }
@Override
public void pay() {
System.out.println("[已支付] 订单已支付,无需重复支付!");
}
@Override
public void ship() {
System.out.println("[已支付] 订单 " + context.getOrderId() + " 已发货!");
context.setState(new ShippedState(context));
}
@Override
public void confirm() {
System.out.println("[已支付] 订单未发货,不能确认收货!");
}
}
// ConcreteState:已发货状态
public class ShippedState implements OrderState {
private OrderContext context;
public ShippedState(OrderContext context) {
this.context = context;
}
@Override
public String getName() { return "已发货"; }
@Override
public void pay() {
System.out.println("[已发货] 订单已支付,无需重复支付!");
}
@Override
public void ship() {
System.out.println("[已发货] 订单已发货,无需重复发货!");
}
@Override
public void confirm() {
System.out.println("[已发货] 订单 " + context.getOrderId() + " 已签收!");
context.setState(new DeliveredState(context));
}
}
// ConcreteState:已签收状态(终态)
public class DeliveredState implements OrderState {
private OrderContext context;
public DeliveredState(OrderContext context) {
this.context = context;
}
@Override
public String getName() { return "已签收"; }
@Override
public void pay() {
System.out.println("[已签收] 订单已完成,不能支付!");
}
@Override
public void ship() {
System.out.println("[已签收] 订单已完成,不能发货!");
}
@Override
public void confirm() {
System.out.println("[已签收] 订单已完成,不能重复确认!");
}
}
// Client
public class Client {
public static void main(String[] args) {
OrderContext order = new OrderContext("ORD20240713001");
order.pay();
order.ship();
order.confirm();
order.pay();
}
}
运行结果:
订单 ORD20240713001 状态切换为: 待支付
[待支付] 订单 ORD20240713001 支付成功!
订单 ORD20240713001 状态切换为: 已支付
[已支付] 订单 ORD20240713001 已发货!
订单 ORD20240713001 状态切换为: 已发货
[已发货] 订单 ORD20240713001 已签收!
订单 ORD20240713001 状态切换为: 已签收
[已签收] 订单已完成,不能支付!
关键体会:
-
状态转换发生在 State 内部 :
PendingState.pay()处理完支付后,调用context.setState(new PaidState(context))自动切换到已支付状态。客户端完全不知道状态转换的细节。 -
Context 只负责转发 :
OrderContext.pay()只做state.pay(),不判断当前是什么状态。行为差异由当前 State 对象决定。 -
消除 if-else :如果没有状态模式,
OrderContext里会充满if (state.equals("PENDING")) { ... } else if (state.equals("PAID")) { ... }的臃肿代码。
四、场景二:游戏角色状态(自动转换 + 条件判断)
业务背景:游戏角色有多个状态:正常(Normal)、加速(SpeedUp)、无敌(Invincible)、死亡(Dead)。角色吃不同道具会触发状态转换:
-
正常状态 → 吃加速蘑菇 → 加速状态(持续 5 秒后自动回到正常)
-
正常/加速状态 → 吃星星 → 无敌状态(持续 3 秒后自动回到之前的状态)
-
任何状态 → 碰到敌人(非无敌状态)→ 死亡状态(终态)
说明:这是软考下午题中常见的 "带条件判断和定时自动回退" 的状态模式场景,状态转换逻辑更复杂。
4.1 代码实现(简化版)
java
// State:角色状态接口
public interface CharacterState {
String getName();
void move();
void eatMushroom();
void eatStar();
void hitEnemy();
}
// Context:游戏角色
public class GameCharacter {
private CharacterState state;
private String name;
public GameCharacter(String name) {
this.name = name;
this.state = new NormalState(this);
}
public void setState(CharacterState state) {
this.state = state;
System.out.println(name + " 状态切换为: " + state.getName());
}
public void move() { state.move(); }
public void eatMushroom() { state.eatMushroom(); }
public void eatStar() { state.eatStar(); }
public void hitEnemy() { state.hitEnemy(); }
public String getName() { return name; }
}
// ConcreteState:正常状态
public class NormalState implements CharacterState {
private GameCharacter character;
public NormalState(GameCharacter character) {
this.character = character;
}
@Override
public String getName() { return "正常"; }
@Override
public void move() {
System.out.println("[" + getName() + "] " + character.getName() + " 正常移动");
}
@Override
public void eatMushroom() {
System.out.println("[" + getName() + "] 吃加速蘑菇,速度提升!");
character.setState(new SpeedUpState(character));
}
@Override
public void eatStar() {
System.out.println("[" + getName() + "] 吃星星,进入无敌状态!");
character.setState(new InvincibleState(character, this));
}
@Override
public void hitEnemy() {
System.out.println("[" + getName() + "] 碰到敌人,死亡!");
character.setState(new DeadState(character));
}
}
// ConcreteState:加速状态
public class SpeedUpState implements CharacterState {
private GameCharacter character;
public SpeedUpState(GameCharacter character) {
this.character = character;
}
@Override
public String getName() { return "加速"; }
@Override
public void move() {
System.out.println("[" + getName() + "] " + character.getName() + " 飞速移动!");
}
@Override
public void eatMushroom() {
System.out.println("[" + getName() + "] 已经是加速状态,加速效果刷新!");
}
@Override
public void eatStar() {
System.out.println("[" + getName() + "] 吃星星,进入无敌状态!");
character.setState(new InvincibleState(character, this));
}
@Override
public void hitEnemy() {
System.out.println("[" + getName() + "] 碰到敌人,加速状态消失,回到正常状态...");
character.setState(new NormalState(character));
}
}
// ConcreteState:无敌状态
public class InvincibleState implements CharacterState {
private GameCharacter character;
private CharacterState previousState;
public InvincibleState(GameCharacter character, CharacterState previousState) {
this.character = character;
this.previousState = previousState;
}
@Override
public String getName() { return "无敌"; }
@Override
public void move() {
System.out.println("[" + getName() + "] " + character.getName() + " 无敌移动,撞飞敌人!");
}
@Override
public void eatMushroom() {
System.out.println("[" + getName() + "] 无敌状态吃蘑菇,无额外效果");
}
@Override
public void eatStar() {
System.out.println("[" + getName() + "] 已经是无敌状态,无敌时间刷新!");
}
@Override
public void hitEnemy() {
System.out.println("[" + getName() + "] 碰到敌人,无敌状态免疫伤害!");
}
public void timeout() {
System.out.println("[" + getName() + "] 无敌时间结束,回退到 " + previousState.getName());
character.setState(previousState);
}
}
// ConcreteState:死亡状态(终态)
public class DeadState implements CharacterState {
private GameCharacter character;
public DeadState(GameCharacter character) {
this.character = character;
}
@Override
public String getName() { return "死亡"; }
@Override
public void move() {
System.out.println("[" + getName() + "] 已死亡,无法移动");
}
@Override
public void eatMushroom() {
System.out.println("[" + getName() + "] 已死亡,无法吃道具");
}
@Override
public void eatStar() {
System.out.println("[" + getName() + "] 已死亡,无法吃道具");
}
@Override
public void hitEnemy() {
System.out.println("[" + getName() + "] 已死亡,不能再死一次");
}
}
// Client
public class Client {
public static void main(String[] args) {
GameCharacter mario = new GameCharacter("马里奥");
mario.move();
mario.eatMushroom();
mario.move();
mario.eatStar();
mario.move();
mario.hitEnemy();
}
}
五、场景三:工作流审批(带条件的状态转换)
业务背景:请假审批流程有多个状态:草稿(Draft)→ 待审批(Pending)→ 已批准(Approved)/ 已拒绝(Rejected)。每个状态下触发不同事件(提交、批准、拒绝、撤回),行为不同,且转换有条件限制(如只有经理能批准,只有提交人能撤回)。
说明:这是软考下午题中常见的 "工作流引擎" 场景,状态转换带有业务条件判断。
5.1 代码实现
java
// State:审批状态接口
public interface ApprovalState {
String getName();
void submit();
void approve();
void reject();
void withdraw();
}
// Context:请假申请
public class LeaveRequest {
private ApprovalState state;
private String applicant;
private String approver;
public LeaveRequest(String applicant, String approver) {
this.applicant = applicant;
this.approver = approver;
this.state = new DraftState(this);
}
public void setState(ApprovalState state) {
this.state = state;
System.out.println("请假申请状态切换为: " + state.getName());
}
public void submit() { state.submit(); }
public void approve() { state.approve(); }
public void reject() { state.reject(); }
public void withdraw() { state.withdraw(); }
public String getApplicant() { return applicant; }
public String getApprover() { return approver; }
}
// ConcreteState:草稿状态
public class DraftState implements ApprovalState {
private LeaveRequest request;
public DraftState(LeaveRequest request) {
this.request = request;
}
@Override
public String getName() { return "草稿"; }
@Override
public void submit() {
System.out.println("[草稿] 申请已提交,进入待审批状态");
request.setState(new PendingState(request));
}
@Override
public void approve() {
System.out.println("[草稿] 申请未提交,不能批准!");
}
@Override
public void reject() {
System.out.println("[草稿] 申请未提交,不能拒绝!");
}
@Override
public void withdraw() {
System.out.println("[草稿] 申请尚未提交,无需撤回");
}
}
// ConcreteState:待审批状态
public class PendingState implements ApprovalState {
private LeaveRequest request;
public PendingState(LeaveRequest request) {
this.request = request;
}
@Override
public String getName() { return "待审批"; }
@Override
public void submit() {
System.out.println("[待审批] 申请已提交,无需重复提交!");
}
@Override
public void approve() {
System.out.println("[待审批] 经理 " + request.getApprover() + " 批准了申请!");
request.setState(new ApprovedState(request));
}
@Override
public void reject() {
System.out.println("[待审批] 经理 " + request.getApprover() + " 拒绝了申请!");
request.setState(new RejectedState(request));
}
@Override
public void withdraw() {
System.out.println("[待审批] 申请人 " + request.getApplicant() + " 撤回了申请!");
request.setState(new DraftState(request));
}
}
// ConcreteState:已批准状态(终态)
public class ApprovedState implements ApprovalState {
private LeaveRequest request;
public ApprovedState(LeaveRequest request) {
this.request = request;
}
@Override
public String getName() { return "已批准"; }
@Override
public void submit() {
System.out.println("[已批准] 申请已批准,无需提交!");
}
@Override
public void approve() {
System.out.println("[已批准] 申请已批准,无需重复批准!");
}
@Override
public void reject() {
System.out.println("[已批准] 申请已批准,不能拒绝!");
}
@Override
public void withdraw() {
System.out.println("[已批准] 申请已批准,不能撤回!");
}
}
// ConcreteState:已拒绝状态(终态)
public class RejectedState implements ApprovalState {
private LeaveRequest request;
public RejectedState(LeaveRequest request) {
this.request = request;
}
@Override
public String getName() { return "已拒绝"; }
@Override
public void submit() {
System.out.println("[已拒绝] 申请已拒绝,可以重新编辑后提交");
request.setState(new DraftState(request));
}
@Override
public void approve() {
System.out.println("[已拒绝] 申请已拒绝,不能批准!");
}
@Override
public void reject() {
System.out.println("[已拒绝] 申请已拒绝,无需重复拒绝!");
}
@Override
public void withdraw() {
System.out.println("[已拒绝] 申请已拒绝,无需撤回!");
}
}
// Client
public class Client {
public static void main(String[] args) {
LeaveRequest request = new LeaveRequest("张三", "李经理");
request.submit();
request.approve();
request.reject();
}
}
六、三种场景对比与演进思路
| 维度 | 场景一:订单状态机 | 场景二:游戏角色状态 | 场景三:工作流审批 |
|---|---|---|---|
| Context | OrderContext(订单) |
GameCharacter(角色) |
LeaveRequest(请假申请) |
| State | OrderState(订单状态) |
CharacterState(角色状态) |
ApprovalState(审批状态) |
| ConcreteState | Pending、Paid、Shipped、Delivered |
Normal、SpeedUp、Invincible、Dead |
Draft、Pending、Approved、Rejected |
| 状态转换触发 | 事件处理完成后自动转换 | 事件处理 + 定时器回退 | 事件处理 + 条件判断 |
| 终态 | Delivered(已签收) |
Dead(死亡) |
Approved / Rejected |
| 状态转换方向 | 单向线性(Pending→Paid→Shipped→Delivered) | 多向(可回退、可跳转) | 多向(Draft↔Pending,Pending→Approved/Rejected) |
| 状态持有 Context | ✅ 是(用于调用 setState()) |
✅ 是 | ✅ 是 |
| 软考考法 | 下午大题(补全状态转换) | 下午大题(补全条件判断) | 下午大题(补全工作流转换) |
七、状态模式 vs 策略模式 vs 模板方法模式:核心对比
| 对比项 | 状态模式 | 策略模式 | 模板方法模式 |
|---|---|---|---|
| 模式分类 | 行为型 | 行为型 | 行为型 |
| 核心思想 | 封装 状态 及状态转换,状态改变时行为自动改变 | 封装 算法族,客户端主动选择并替换 | 定义 算法骨架,子类实现特定步骤 |
| 切换触发者 | 状态对象内部 根据条件自动调用 context.setState() 转换 |
客户端 主动调用 context.setStrategy() 选择 |
无切换(子类固定) |
| Context 与实现类关系 | Context 组合 State(双向关联:State 持有 Context 引用) | Context 组合 Strategy(单向关联) | 抽象类 继承(子类 extends 父类) |
| 状态转换逻辑位置 | 写在 ConcreteState 内部 | 无状态转换(只是算法替换) | 无状态转换 |
| 客户端角色 | 客户端 触发事件 (context.request()),不参与状态选择 |
客户端 选择策略 并注入(context.setStrategy()) |
客户端 调用 模板方法 |
| 新增扩展 | 新增状态类,可能需修改状态转换逻辑 | 新增策略类,不改 Context | 新增子类,实现抽象方法 |
| 典型应用 | 订单状态机、游戏角色状态、工作流审批 | 促销策略、支付方式、排序算法 | 数据导入模板、报表生成模板 |
| 软考判断 | 看到 "状态转换"、"自动改变行为"、"状态机" → 状态 | 看到 "多种算法可替换"、"客户端选择" → 策略 | 看到 "算法骨架"、"子类实现特定步骤" → 模板方法 |
记忆口诀:状态是 "状态自己变"(自动转换),策略是 "客户端选算法"(主动切换),模板方法是 "父类定骨架子类填坑"(继承实现)。
八、软考高频考点与易混淆辨析
8.1 高频考点
| 考点 | 内容 |
|---|---|
| 模式分类 | 行为型模式(GoF 23 正式成员) |
| 核心三角色 | Context(上下文)、State(抽象状态)、ConcreteState(具体状态) |
| 核心思想 | 将状态封装为独立类,状态对象自己管理状态转换规则,Context 行为随状态改变而自动改变 |
| Context 与 State 关系 | 双向关联:Context 持有 State(组合),State 持有 Context(用于状态转换) |
| 状态转换逻辑位置 | 写在 ConcreteState 的方法内部(根据条件调用 context.setState(new NextState())) |
| 消除 if-else | 状态模式的核心价值之一:避免 Context 中出现大量 if (state == X) { ... } |
| 与策略模式区别 | 状态:状态内部 自动转换 ;策略:客户端 主动选择 。这是两者 最本质的区别 |
| 与模板方法区别 | 状态:组合 + 状态自动转换;模板方法:继承 + 算法骨架固定 |
| 适用场景 | ① 对象行为取决于状态,且状态改变时行为改变 ② 存在复杂的条件状态判断 ③ 状态转换有固定规则(状态机) |
8.2 易混淆辨析:状态 vs 策略(最难辨析)
| 对比项 | 状态模式 | 策略模式 |
|---|---|---|
| UML 结构 | Context 组合 State,State 持有 Context 引用 | Context 组合 Strategy,Strategy 不持有 Context |
| 关联方向 | 双向(Context ↔ State) | 单向(Context → Strategy) |
| 切换控制权 | 内部(State 对象自己决定何时转换) | 外部(客户端通过 setter 注入) |
| 切换逻辑位置 | 写在 State 类内部 | 写在 客户端代码 或 Context 外部 |
| 状态转换 | ✅ 有(核心特征) | ❌ 无(只是替换算法) |
| ConcreteState 持有 Context | ✅ 必须 (否则无法调用 setState()) |
❌ 不需要(Strategy 只负责算法) |
| Client 代码 | context.request();(只触发事件) |
context.setStrategy(new Xxx()); context.execute();(选择 + 执行) |
| 典型例子 | 订单状态机、电梯状态、游戏角色 | 促销策略、支付方式、排序算法 |
| 软考判断 | 看到 "状态转换"、"自动切换"、"状态机" → 状态 | 看到 "客户端选择"、"算法替换"、"动态切换" → 策略 |
终极判断技巧 :看 ConcreteState/ConcreteStrategy 是否持有 Context 引用,且是否在方法内部调用
context.setState()或context.setStrategy()。如果 内部自动转换状态 → 状态模式;如果 只是被注入后执行算法 → 策略模式。
九、真题风格模拟与代码填空
模拟题 1(上午选择题)
以下关于状态模式的叙述中,正确的是()。
A. 状态模式属于结构型模式,主要用于封装对象的状态转换逻辑
B. 在状态模式中,状态转换由客户端通过
setState()主动选择并触发C. 状态模式与策略模式的本质区别在于:状态模式的状态转换由状态对象内部自动完成,策略模式的算法替换由客户端主动选择
D. 状态模式中,ConcreteState 不需要持有 Context 的引用,因为状态转换由 Context 自己管理
答案:C
解析:
-
A 错误:状态模式属于 行为型 模式,不是结构型。
-
B 错误:状态转换由 状态对象内部 根据条件自动完成,客户端通常只触发事件(如
context.pay()),不直接调用setState()。 -
C 正确:这是状态模式与策略模式 最本质的区别 ------ 状态转换的触发者不同(内部自动 vs 外部主动)。
-
D 错误:ConcreteState 必须 持有 Context 引用,否则无法在状态处理完成后调用
context.setState()进行状态转换。
模拟题 2(下午代码填空 --- 补全状态转换逻辑)
某系统使用状态模式实现电梯控制。
ElevatorState接口提供openDoor()、closeDoor()、run()、stop()方法。ClosedState在openDoor()处理后切换到OpenedState。请补全(1)~(4)。
java
// State
interface ElevatorState {
String getName();
void openDoor();
void closeDoor();
void run();
void stop();
}
// Context
class Elevator {
private ElevatorState state;
public Elevator() {
this.state = new ClosedState(this);
}
public void (1)______(ElevatorState state) {
this.state = state;
System.out.println("电梯状态: " + state.getName());
}
public void openDoor() { state.openDoor(); }
public void closeDoor() { state.closeDoor(); }
public void run() { state.run(); }
public void stop() { state.stop(); }
}
// ConcreteState:关门状态
class ClosedState implements ElevatorState {
private (2)______ elevator;
public ClosedState(Elevator elevator) {
this.elevator = elevator;
}
public String getName() { return "关门"; }
public void openDoor() {
System.out.println("[关门] 门打开");
(3)______.(4)______(new OpenedState(elevator));
}
public void closeDoor() {
System.out.println("[关门] 门已经是关闭的");
}
public void run() {
System.out.println("[关门] 电梯开始运行");
elevator.setState(new RunningState(elevator));
}
public void stop() {
System.out.println("[关门] 电梯已经停止");
}
}
// 其他状态类省略...
public class Client {
public static void main(String[] args) {
Elevator elevator = new Elevator();
elevator.openDoor();
}
}
答案:
-
(1)
setState -
(2)
Elevator(或ElevatorContext) -
(3)
elevator -
(4)
setState
阅卷要点:
(1) 必须是
setState,这是 Context 的标准状态切换方法名。(2) 必须是
Elevator(Context 类型),ConcreteState 必须持有 Context 引用才能进行状态转换。如果写ElevatorState会 零分(State 是接口,不能持有自己)。(3) 必须是
elevator(持有的 Context 引用),通过它调用 Context 的setState()方法。(4) 必须是
setState,这是状态转换的核心方法调用。如果写setStrategy会 零分(那是策略模式的方法名)。
模拟题 3(下午代码填空 --- 补全 Context 委托与 State 条件转换)
某系统使用状态模式实现 TCP 连接管理。
TCPState接口提供open()、close()、acknowledge()方法。TCPClosed在open()后切换到TCPEstablished。TCPEstablished在close()后切换到TCPClosed。请补全(1)~(3)。
java
// State
interface TCPState {
void open();
void close();
void acknowledge();
}
// Context
class TCPConnection {
private TCPState state;
public TCPConnection() {
this.state = new TCPClosed(this);
}
public void setState(TCPState state) {
this.state = state;
}
public void open() { (1)______; }
public void close() { (2)______; }
public void acknowledge() { state.acknowledge(); }
}
// ConcreteState:关闭状态
class TCPClosed implements TCPState {
private TCPConnection connection;
public TCPClosed(TCPConnection connection) {
this.connection = connection;
}
public void open() {
System.out.println("[关闭] 打开连接");
connection.setState(new TCPEstablished(connection));
}
public void close() {
System.out.println("[关闭] 连接已经是关闭的");
}
public void acknowledge() {
System.out.println("[关闭] 连接未打开,无法确认");
}
}
// ConcreteState:已建立状态
class TCPEstablished implements TCPState {
private TCPConnection connection;
public TCPEstablished(TCPConnection connection) {
this.connection = connection;
}
public void open() {
System.out.println("[已建立] 连接已打开,无需重复打开");
}
public void close() {
System.out.println("[已建立] 关闭连接");
(3)______;
}
public void acknowledge() {
System.out.println("[已建立] 确认收到数据");
}
}
public class Client {
public static void main(String[] args) {
TCPConnection conn = new TCPConnection();
conn.open();
conn.acknowledge();
conn.close();
}
}
答案:
-
(1)
state.open() -
(2)
state.close() -
(3)
connection.setState(new TCPClosed(connection))
阅卷要点:
(1)(2) 必须是
state.open()和state.close(),Context 将事件委托给当前 State 对象处理。这是状态模式的核心:Context 只负责转发,行为由 State 决定。(3) 必须是
connection.setState(new TCPClosed(connection)),在TCPEstablished处理完close()事件后,自动转换回TCPClosed状态。注意:必须通过connection(持有的 Context 引用)调用setState(),且必须创建新的TCPClosed对象传入connection自身。
十、常见陷阱与注意事项
陷阱 1:误认为状态是结构型模式
状态模式属于 行为型 模式,因为它关注的是 对象在不同状态下的行为变化 以及 状态之间的转换 ,而不是类与类之间的结构关系。软考上午题如果问"以下属于结构型模式的是",选项里出现"状态" 不能选。
陷阱 2:状态转换由客户端主动触发
这是与策略模式 最本质的混淆点 。状态模式的核心是 状态自动转换:
-
❌ 错误(策略模式风格):
client → context.setState(new NextState())(客户端直接切换状态) -
✅ 正确(状态模式风格):
client → context.request() → state.handle() → state 内部调用 context.setState(new NextState())(客户端只触发事件,状态内部自动转换)
软考代码填空里,如果状态转换逻辑写在客户端代码里(如 if (condition) { context.setState(new XxxState()); }),那就是 策略模式 的用法,不是状态模式。状态模式要求状态转换封装在 State 内部。
陷阱 3:ConcreteState 不持有 Context 引用
状态模式要求 双向关联 :Context 持有 State,State 也持有 Context。如果 State 不持有 Context,就无法在状态处理完成后调用 context.setState() 进行状态转换。软考代码填空里,ConcreteState 的构造器必须接收 Context 参数(如 public ClosedState(Elevator elevator)),且必须保存为成员变量。
陷阱 4:与策略模式混淆
| 状态模式 | 策略模式 |
|---|---|
| State 持有 Context 引用 | Strategy 不持有 Context 引用 |
| 状态转换在 State 内部 完成 | 算法替换在 客户端 完成 |
| 有 状态转换 逻辑 | 无状态转换,只是 算法替换 |
| 客户端只 触发事件 | 客户端 选择并注入 策略 |
终极判断技巧 :看 ConcreteState/ConcreteStrategy 是否持有 Context 引用,且是否在方法内部调用 context.setState()。如果内部自动转换 → 状态;如果只是被注入后执行算法 → 策略。
陷阱 5:Context 内部用 if-else 判断状态
这是 违背状态模式初衷 的写法。状态模式的目的就是 消除 Context 中的 if-else:
- ❌ 错误:
java
public void pay() {
if (state instanceof PendingState) { ... }
else if (state instanceof PaidState) { ... }
}
- ✅ 正确:
java
public void pay() {
state.pay(); // 直接委托给当前 State,State 自己决定行为
}
软考阅卷时,如果 Context 的 request() 方法里出现了 instanceof 或 if-else 判断状态类型,通常会 扣分,因为这违背了状态模式的设计意图。
陷阱 6:状态类命名不规范
软考中,状态类通常命名为 XxxState(如 PendingState、PaidState),Context 类命名为 XxxContext(如 OrderContext、ElevatorContext)。如果代码填空里要求补全类名,要注意命名规范。State 接口通常命名为 State 或 XxxState。
陷阱 7:忘记在 Context 构造器中设置初始状态
Context 创建时必须有一个初始状态,否则 state 为 null,第一次调用 request() 会空指针。软考代码填空里,Context 的构造器中通常有 this.state = new InitialState(this); 这样的初始化代码。如果遗漏,会判错。
十一、总结
| 要点 | 内容 |
|---|---|
| 定义 | 允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类 |
| 分类 | 行为型模式(GoF 23 正式成员) |
| 核心三角色 | Context(上下文)、State(抽象状态)、ConcreteState(具体状态) |
| 核心思想 | 将状态封装为独立类,状态对象自己管理状态转换规则,Context 行为随状态改变而自动改变 |
| Context 与 State 关系 | 双向关联:Context 组合 State,State 持有 Context 引用(用于状态转换) |
| 状态转换逻辑位置 | 写在 ConcreteState 的方法内部(根据条件调用 context.setState(new NextState())) |
| 消除 if-else | 避免 Context 中出现大量 if (state == X) { ... },将状态行为分发到各个 State 类 |
| 与策略模式区别 | 状态:状态对象 内部自动转换 ;策略:客户端 主动选择 算法。这是两者最本质的区别 |
| 与模板方法区别 | 状态:组合 + 状态自动转换;模板方法:继承 + 算法骨架固定 |
| 适用场景 | 对象行为取决于状态、存在复杂条件状态判断、状态转换有固定规则(状态机) |
| 软考重点 | 代码填空(补全 State 内部的状态转换逻辑 context.setState());与策略模式的辨析(最高频);Context 的委托调用 |
| 答题技巧 | 看到 "状态转换"、"状态机"、"自动改变行为"、"状态内部调用 setState" → 状态;看到 Context 与 State 双向关联 → 确认状态 |
系列预告 :下一篇将讲 模板方法模式 ------ 当算法的骨架固定但某些步骤需要子类灵活实现时,如何用继承的方式定义算法框架,让子类填充可变部分。状态与模板方法的辨析也是软考常见考点,咱们下回见。