状态设计模式
状态设计模式是一种行为设计模式,它允许对象在其内部状态更改时更改其行为,就像它在运行时切换到不同的类一样。
它在以下情况下特别有用:
对象可以处于许多不同状态之一,每种状态都有不同的行为。
对象的行为取决于当前上下文,并且该上下文会随着时间的推移而变化。
你希望避免检查每个可能状态的大型、整体式或语句。if-elseswitch
当面对这种情况时,开发人员通常首先在类中使用条件逻辑来切换基于状态变量的行为。
例如,类 可能会使用 块来确定要执行的作,具体取决于它是处于“草稿”、“审阅”还是“已发布”状态。Documentif-else
但随着状态数量的增长,这种方法变得难以扩展、难以测试,并且违反了开放/封闭原则——任何新状态都需要修改现有逻辑,从而增加了破坏当前功能的风险。
状态模式通过将每个状态封装到自己的类中,并让上下文对象将行为委托给当前状态对象来解决这个问题。这使您的代码更易于扩展、重用和维护,而不会因条件而使核心逻辑变得混乱。
让我们通过一个真实世界的示例来了解如何应用状态模式以干净、可扩展和面向对象的方式管理动态行为。
问题:管理自动售货机状态
想象一下,您正在构建一个简单的自动售货机系统。从表面上看,这似乎是一项简单的任务:接受资金,分配产品,然后回到闲置状态。
但在幕后,机器的行为需要根据其当前状态而变化。
在任何给定时间,自动售货机只能处于一种状态,例如:
IdleState – 等待用户输入(未选择任何内容,未插入任何资金)。
ItemSelectedState – 已选择项目,等待付款。
HasMoneyState – 已插入资金,等待分配所选项目。
DispensingState – 机器正在主动分配项目。
该机器支持一些面向用户的作:
selectItem(String itemCode)– 选择要购买的商品
insertCoin(double amount)– 插入所选项目的付款
dispenseItem()– 触发物品分配过程
这些方法中的每一种都应根据计算机的当前状态以不同的方式运行。
例如:
在 机器打开时 调用(未选择任何项目,未插入任何资金)不应执行任何作或显示错误。dispenseItem()IdleState
在选择项目之前调用可能会被禁止或排队。insertCoin()
应 忽略或推迟期间调用,直到项目被分配。selectItem()DispensingState
天真的方法
一种常见但有缺陷的方法是 使用 or 语句在整体类中手动管理状态转换:VendingMachineif-elseswitch
public class VendingMachine {private enum State {IDLE, ITEM_SELECTED, HAS_MONEY, DISPENSING}private State currentState = State.IDLE;private String selectedItem = "";private double insertedAmount = 0.0;public void selectItem(String itemCode) {switch (currentState) {case IDLE:selectedItem = itemCode;currentState = State.ITEM_SELECTED;break;case ITEM_SELECTED:System.out.println("Item already selected");break;case HAS_MONEY:System.out.println("Payment already received for item");break;case DISPENSING:System.out.println("Currently dispensing");break;}}public void insertCoin(double amount) {switch (currentState) {case IDLE:System.out.println("No item selected");break;case ITEM_SELECTED:insertedAmount = amount;System.out.println("Inserted $" + amount + " for item");currentState = State.HAS_MONEY;break;case HAS_MONEY:System.out.println("Money already inserted");break;case DISPENSING:System.out.println("Currently dispensing");break;}}public void dispenseItem() {switch (currentState) {case IDLE:System.out.println("No item selected");break;case ITEM_SELECTED:System.out.println("Please insert coin first");break;case HAS_MONEY:System.out.println("Dispensing item '" + selectedItem);currentState = State.DISPENSING;// Simulate delay and completiontry {Thread.sleep(1000);} catch (InterruptedException e) {Thread.currentThread().interrupt();}System.out.println("Item dispensed successfully.");resetMachine();break;case DISPENSING:System.out.println("Already dispensing. Please wait.");break;}}private void resetMachine() {selectedItem = "";insertedAmount = 0.0;currentState = State.IDLE;}
}
这种方法有什么问题?
虽然使用 with 语句适用于小型、可预测的系统,但这种方法不能很好地扩展。enumswitch
- 1. 代码混乱
所有与状态相关的逻辑都塞进一个类 () 中,导致每个方法( 、 、 等)都出现大而重复的块。VendingMachineswitchif-elseselectItem()insertCoin()dispenseItem()
这导致:
难以阅读和推理的代码
跨多个方法重复检查状态
多个开发人员接触同一文件时的逻辑脆弱
- 2. 难以扩展
假设你想引入新的状态,例如:
OutOfStockState– 当所选商品售罄时
MaintenanceState– 机器正在维修时
要支持这些,您需要:
更新 每个方法中的每个开关块
在多个位置添加逻辑
破坏现有功能的风险
这违反了开放/封闭原则——当系统应该开放扩展时,系统可以进行修改。 - 3. 违反单一责任原则
该 班现在负责:VendingMachine
管理状态转换
实施业务规则
执行特定于状态的逻辑
这种紧密耦合使该类成为单片、难以测试且耐变化。
我们真正需要什么
我们需要将与每个状态关联的行为封装到它自己的类中——这样自动售货机就可以将工作委托给当前状态对象,而不是在内部管理它。
这将使我们能够:
避免疯狂的开关情况
添加或删除状态而不修改核心类
保持每个状态的逻辑隔离和可重用
这正是状态设计模式所实现的。
状态模式
State 模式允许对象(Context)在其内部状态更改时更改其行为。该对象似乎更改了其类,因为它的行为现在已委托给不同的状态对象。
状态模式不是将特定于状态的行为嵌入上下文类本身,而是将该行为提取到单独的类中,每个类代表一个不同的状态。上下文对象保存对状态对象的引用,并将其作委托给它。
这会产生更干净、更模块化和可扩展的代码。
定义一个 State 接口(或抽象类),该接口声明表示 Context 可以执行的作的方法。
创建 ConcreteState 类,每个类都实现 State 接口。每个 ConcreteState 类都实现特定于 Context 的特定状态的行为。
Context类维护定义其当前状态的ConcreteState子类的实例。
在 Context 上调用作时,它会将该作委托给其当前 State 对象。
ConcreteState 对象通常负责将 Context 转换为新状态。
类图
- 1. 状态接口(例如MachineState)
声明与上下文支持的作相对应的方法(例如,, ,)。selectItem()insertCoin()dispenseItem()
这些方法通常将上下文作为参数,因此状态可以触发转换或作上下文数据。
作为所有具体状态的共同合同。 - 2. 具体状态(例如, IdleStateItemSelectedState)
实现接口 。State
为 每个作定义特定于状态的行为。
通常负责 在发生特定作时将上下文转换为另一种状态。
还可以包括特定于状态的逻辑(例如,验证、消息传递)。 - 3. 上下文(例如VendingMachine)
维护对当前对象的引用 。State
定义每个作(、 等)的方法。selectItem()insertCoin()
委托对当前状态的调用 — 状态对象处理逻辑。
提供一种方法,例如允许状态之间的转换。setState()
实现状态模式
我们将应用状态模式来分离关注点,并使自动售货机更易于管理和扩展,而不是使用 or 语句将状态转换和行为硬编码为单个整体类。if-elseswitch
第一步是定义一个 接口,用于声明自动售货机支持的所有作。MachineState
- 1. 定义状态接口
该接口表示所有州都必须遵循的契约。它声明与自动售货机面向用户的作相对应的方法。
public interface MachineState {void selectItem(VendingMachine context, String itemCode);void insertCoin(VendingMachine context, double amount);void dispenseItem(VendingMachine context);
}
每个状态都将实现此接口,定义自动售货机在该状态下的行为方式。
- 2. 实现具体状态类
每个状态类将实现 接口并为每个作定义其行为。MachineState
🟡 空闲状态
public class IdleState implements MachineState {@Overridepublic void selectItem(VendingMachine context, String itemCode) {System.out.println("Item selected: " + itemCode);context.setSelectedItem(itemCode);context.setState(new ItemSelectedState());}@Overridepublic void insertCoin(VendingMachine context, double amount) {System.out.println("Please select an item before inserting coins.");}@Overridepublic void dispenseItem(VendingMachine context) {System.out.println("No item selected. Nothing to dispense.");}
}
🟠 ItemSelected状态
public class ItemSelectedState implements MachineState {@Overridepublic void selectItem(VendingMachine context, String itemCode) {System.out.println("Item already selected: " + context.getSelectedItem());}@Overridepublic void insertCoin(VendingMachine context, double amount) {System.out.println("Inserted $" + amount + " for item: " + context.getSelectedItem());context.setInsertedAmount(amount);context.setState(new HasMoneyState());}@Overridepublic void dispenseItem(VendingMachine context) {System.out.println("Insert coin before dispensing.");}
}
🟢 有钱州
public class HasMoneyState implements MachineState {@Overridepublic void selectItem(VendingMachine context, String itemCode) {System.out.println("Cannot change item after inserting money.");}@Overridepublic void insertCoin(VendingMachine context, double amount) {System.out.println("Money already inserted.");}@Overridepublic void dispenseItem(VendingMachine context) {System.out.println("Dispensing item: " + context.getSelectedItem());context.setState(new DispensingState());// Simulate dispensingtry { Thread.sleep(1000); } catch (InterruptedException e) {Thread.currentThread().interrupt();}System.out.println("Item dispensed successfully.");context.reset();}
}
🔵 分配状态
public class DispensingState implements MachineState {@Overridepublic void selectItem(VendingMachine context, String itemCode) {System.out.println("Please wait, dispensing in progress.");}@Overridepublic void insertCoin(VendingMachine context, double amount) {System.out.println("Please wait, dispensing in progress.");}@Overridepublic void dispenseItem(VendingMachine context) {System.out.println("Already dispensing. Please wait.");}
}
- 3. 实现上下文 (VendingMachine)
类(我们的上下文)将维护对当前状态的引用,并将所有作委托给它。VendingMachine
public class VendingMachine {private MachineState currentState;private String selectedItem;private double insertedAmount;public VendingMachine() {this.currentState = new IdleState(); // Initial state}public void setState(MachineState newState) {this.currentState = newState;}public void setSelectedItem(String itemCode) {this.selectedItem = itemCode;}public void setInsertedAmount(double amount) {this.insertedAmount = amount;}public String getSelectedItem() {return selectedItem;}public double getInsertedAmount() {return insertedAmount;}public void selectItem(String itemCode) {currentState.selectItem(this, itemCode);}public void insertCoin(double amount) {currentState.insertCoin(this, amount);}public void dispenseItem() {currentState.dispenseItem(this);}public void reset() {this.selectedItem = "";this.insertedAmount = 0.0;this.currentState = new IdleState();}
}
客户端代码
public class VendingMachineApp {public static void main(String[] args) {VendingMachine vm = new VendingMachine();vm.insertCoin(1.0); // Invalid in IdleStatevm.selectItem("A1");vm.insertCoin(1.5);vm.dispenseItem();System.out.println("\n--- Second Transaction ---");vm.selectItem("B2");vm.insertCoin(2.0);vm.dispenseItem();}
}
通过使用状态模式,我们将僵化的、条件密集的实现转变为一个干净、灵活的架构,其中行为和转换被明确定义、解耦且易于维护。