系列定位:软考软件设计师 / 系统架构设计师 — 行为型模式专题第 2 讲

考察分值:上午题 1-2 分,下午题常作为代码填空或类图识别出现

难度等级:⭐⭐⭐⭐☆(与策略模式极易混淆,状态自动转换是核心判分点)


一、考纲定位与模式定义

1.1 考纲要求

状态模式在软考中属于 行为型模式 的中高难度内容。考察形式包括:

  • 上午选择题:判断场景描述所属模式;识别状态模式与策略模式的本质区别(自动转换 vs 客户端选择);判断类图中 State 与 Context 的双向关联关系;识别状态转换逻辑的位置

  • 下午设计题:补全 Context 中 setState() 方法的调用逻辑;补全 ConcreteState 中 handle() 方法内根据条件转换到下一个状态的代码;识别类图中 Context、State、ConcreteState 三个角色及状态转换箭头

1.2 模式定义

状态模式:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。

核心意图:当一个对象的行为取决于它的状态,且状态改变时需要改变行为,同时状态转换有固定的规则(如 "待支付 → 已支付 → 已发货 → 已签收"),如果将所有状态的行为和转换逻辑都写在一个类里,会导致类臃肿、充斥着大量的 if-elseswitch-case。状态模式将 每种状态封装为独立的类,状态对象自己管理 状态转换规则,Context 只需将请求委托给当前状态对象处理。当状态改变时,Context 的行为看起来就像换了一个类。

通俗理解

电梯有多个状态:关门、开门、运行、停止。每个状态下按同一个按钮(如 "开门按钮"),行为完全不同:

  • 关门状态 → 按开门按钮 → 门打开

  • 开门状态 → 按开门按钮 → 无反应(已经是开着的)

  • 运行状态 → 按开门按钮 → 报警(运行中不能开门)

  • 停止状态 → 按开门按钮 → 门打开

如果把这些逻辑都写在 Elevator 类里,就是一堆 if (state == CLOSED) { ... } else if (state == RUNNING) { ... }。状态模式的思路是:把每种状态变成一个独立的类(ClosedStateOpenStateRunningStateStoppedState),每个状态类自己实现 "按开门按钮" 的行为,并且自己决定处理完事件后是否要转换到下一个状态(如关门状态处理完开门请求后,自动转换到开门状态)。电梯(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 代码实现
// 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 代码实现(简化版)
// 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 代码实现
// 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();
    }
}

六、三种场景对比与演进思路

维度场景一:订单状态机场景二:游戏角色状态场景三:工作流审批
ContextOrderContext(订单)GameCharacter(角色)LeaveRequest(请假申请)
StateOrderState(订单状态)CharacterState(角色状态)ApprovalState(审批状态)
ConcreteStatePendingPaidShippedDeliveredNormalSpeedUpInvincibleDeadDraftPendingApprovedRejected
状态转换触发事件处理完成后自动转换事件处理 + 定时器回退事件处理 + 条件判断
终态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() 方法。ClosedStateopenDoor() 处理后切换到 OpenedState。请补全(1)~(4)。

// 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() 方法。TCPClosedopen() 后切换到 TCPEstablishedTCPEstablishedclose() 后切换到 TCPClosed。请补全(1)~(3)。

// 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

  • ❌ 错误:

public void pay() {
    if (state instanceof PendingState) { ... }
    else if (state instanceof PaidState) { ... }
}
  • ✅ 正确:

public void pay() {
    state.pay();  // 直接委托给当前 State,State 自己决定行为
}

软考阅卷时,如果 Context 的 request() 方法里出现了 instanceofif-else 判断状态类型,通常会 扣分,因为这违背了状态模式的设计意图。

陷阱 6:状态类命名不规范

软考中,状态类通常命名为 XxxState(如 PendingStatePaidState),Context 类命名为 XxxContext(如 OrderContextElevatorContext)。如果代码填空里要求补全类名,要注意命名规范。State 接口通常命名为 StateXxxState

陷阱 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 双向关联 → 确认状态

系列预告:下一篇将讲 模板方法模式 —— 当算法的骨架固定但某些步骤需要子类灵活实现时,如何用继承的方式定义算法框架,让子类填充可变部分。状态与模板方法的辨析也是软考常见考点,咱们下回见。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐