【软考设计模式】状态模式:状态驱动行为自动转换与代码填空精讲

系列定位:软考软件设计师 / 系统架构设计师 --- 行为型模式专题第 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 代码实现
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 PendingPaidShippedDelivered NormalSpeedUpInvincibleDead DraftPendingApprovedRejected
状态转换触发 事件处理完成后自动转换 事件处理 + 定时器回退 事件处理 + 条件判断
终态 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)。

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() 方法。TCPClosedopen() 后切换到 TCPEstablishedTCPEstablishedclose() 后切换到 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() 方法里出现了 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 双向关联 → 确认状态

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

相关推荐
饮茶三千2 小时前
电子保函模板编辑器实战二:HTML→FTL 编译转换的设计与实现
前端·vue.js·设计模式
Kel2 小时前
Node.js 本质:从全脉络视角理解运行时原理
javascript·设计模式·node.js
码哥字节3 小时前
11 万 Star 的 Spec Kit,90% 的工程师只用它解决了 10% 的问题
设计模式·vibe coding·github spec kit
我登哥MVP4 小时前
走进 Gang of Four 设计模式:访问者模式
java·设计模式·访问者模式·原型模式
饮茶三千5 小时前
基于 WangEditor 的富文本编辑器定制实践
前端·vue.js·设计模式
折哥的程序人生 · 物流技术专研6 小时前
Java 23 种设计模式:从踩坑到精通 | 番外:状态 vs 策略 —— if-else 消除的两条路,你走对了吗?
java·spring·状态模式·策略模式·java面试·java设计模式·从踩坑到精通
xiaotie10057 小时前
给deerflow2.0增加模型Token 使用数量显示的功能
状态模式
ttod_qzstudio7 小时前
【软考设计模式】中介者模式:网状交互的解耦与集中调度精讲
设计模式·中介者模式
饼干哥哥1 天前
一夜之间,全世界的 Agent 能力提高了一个档次
设计模式·agent·代码规范