状态模式
状态模式是一种行为型设计模式,它允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
核心思想:
将与特定状态相关的行为局部化,并且将不同状态的行为分割开来,封装到不同的状态类中。Context 对象(即需要根据状态改变行为的对象)将与状态相关的操作委托给当前的状态对象。当 Context 对象的状态改变时,它会改变其持有的状态对象的引用。
解决的问题:
当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变行为时,通常会在对象内部使用大量的条件语句(if/else 或 switch)。状态模式可以将这些分支逻辑分散到不同的状态类中,从而使 Context 类更加简洁,并且更容易添加新的状态和行为。
主要角色:
Context (环境类/上下文类):
定义了客户端感兴趣的接口。
维护一个 State 对象的实例,这个实例定义了对象的当前状态。
将所有与状态相关的请求委托给当前的 State 对象。
通常还提供一个方法来改变当前状态。
State (状态接口/抽象状态类):
定义了一个接口,封装与 Context 的一个特定状态相关的行为。
通常包含一组方法,对应 Context 可能执行的操作。
ConcreteState (具体状态类):
实现了 State 接口。
每一个具体状态类都实现了属于特定状态的行为。
在处理完某个请求后,具体状态类通常会决定 Context 的下一个状态(即状态转换)。
图示 (简化版 UML 思路):
+-----------------+ has a +-----------+ | Context |------------------>| State | |-----------------| |-----------| | - currentState | | + handle()| |-----------------| +-----------+ | + request() | ^ | + setState() | | (implements) +-----------------+ | | delegates to current state | +-------------------------------------+ /|\ | +----------------------+----------------------+ | | +-------------------+ +-------------------+ | ConcreteStateA | | ConcreteStateB | |-------------------| |-------------------| | + handle() |<- (may transition to) ->| + handle() | +-------------------+ +-------------------+
一个生活中的例子:自动售货机
一个自动售货机有多种状态:
NoCoinState (没有投币状态)
HasCoinState (已投币状态)
SoldState (商品售出状态)
SoldOutState (商品售罄状态)
根据当前状态,售货机对用户的操作(投币、按按钮、退币)会有不同的反应:
没有投币状态:
投币 -> 转换到 HasCoinState。
按按钮 ->提示请先投币。
已投币状态:
投币 -> 提示已投币。
按按钮 -> 检查是否有货,有货则转换到 SoldState 并出货,无货则提示并转换到 SoldOutState(如果刚好卖完)或保持 HasCoinState 并提示用户选择其他商品。
退币 -> 退还硬币,转换到 NoCoinState。
商品售出状态:
(出货动作完成后)如果还有货 -> 转换到 NoCoinState。
(出货动作完成后)如果没货了 -> 转换到 SoldOutState。
其他操作 -> 提示正在出货。
商品售罄状态:
投币 -> 退币,提示已售罄。
按按钮 -> 提示已售罄。
优点:
将与特定状态相关的行为局部化,并且将不同状态的行为分割开来:每个状态的逻辑都封装在自己的类中,使得代码更加清晰、易于理解和维护。
使得状态转换更加明确:状态转换的逻辑可以放在状态类内部或者 Context 类内部,使得状态之间的切换清晰可见。
易于增加新的状态和转换:增加一个新的状态只需要增加一个新的具体状态类,并修改相关的状态转换逻辑,符合开闭原则(对扩展开放,对修改关闭)。
消除了庞大的条件分支语句:将 if/else 或 switch 语句转化为状态对象的不同实现,使得 Context 类更加简洁。
缺点:
类S数量增多:状态模式会增加系统中类和对象的个数,如果状态非常多,会导致类爆炸。
模式结构对初学者可能显得复杂:如果状态转换逻辑非常简单,或者状态数量很少,使用状态模式可能会显得小题大做。
何时使用状态模式?
一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变行为。
代码中包含大量与对象状态有关的条件语句。
当一个操作中含有庞大的多分支条件语句,且这些分支依赖于该对象的状态时。
Java 示例 (简化的文档审批流程):
假设我们有一个文档对象,它有以下几种状态:
DraftState (草稿状态)
ModerationState (审核中状态)
PublishedState (已发布状态)
// 1. State (状态接口) interface DocumentState { void handleSubmit(DocumentContext document); // 提交审批 void handleApprove(DocumentContext document); // 审批通过 void handleReject(DocumentContext document); // 审批拒绝 void handlePublish(DocumentContext document); // 发布 String getStateName(); } // 2. ConcreteState (具体状态类) class DraftState implements DocumentState { @Override public void handleSubmit(DocumentContext document) { System.out.println("Document submitted for moderation."); document.setState(new ModerationState()); // 状态转换 } @Override public void handleApprove(DocumentContext document) { System.out.println("Error: Document is in draft state, cannot approve directly."); } @Override public void handleReject(DocumentContext document) { System.out.println("Error: Document is in draft state, cannot reject."); } @Override public void handlePublish(DocumentContext document) { System.out.println("Error: Document must be approved before publishing."); } @Override public String getStateName() { return "Draft"; } } class ModerationState implements DocumentState { @Override public void handleSubmit(DocumentContext document) { System.out.println("Error: Document is already under moderation."); } @Override public void handleApprove(DocumentContext document) { System.out.println("Document approved by moderator."); // 假设审批通过后直接可以发布 (或者可以有更复杂的状态,如 ApprovedNotPublishedState) // 为了简化,我们这里直接设置为可以发布,但还未发布 // 实践中,可能需要一个 ApprovedState,然后由该状态处理发布请求 // 或者,审批通过后自动发布(如果业务是这样) // 这里我们让它变成一个“准备发布”的状态,等待发布指令 document.setState(new ApprovedState()); // 假设有一个ApprovedState } @Override public void handleReject(DocumentContext document) { System.out.println("Document rejected by moderator. Reverted to draft."); document.setState(new DraftState()); // 状态转换 } @Override public void handlePublish(DocumentContext document) { System.out.println("Error: Document is under moderation, cannot publish yet."); } @Override public String getStateName() { return "Moderation"; } } // 新增一个审批通过但未发布的状态 class ApprovedState implements DocumentState { @Override public void handleSubmit(DocumentContext document) { System.out.println("Error: Document is already approved."); } @Override public void handleApprove(DocumentContext document) { System.out.println("Error: Document is already approved."); } @Override public void handleReject(DocumentContext document) { System.out.println("Document approval rescinded. Reverted to draft."); document.setState(new DraftState()); } @Override public void handlePublish(DocumentContext document) { System.out.println("Document published successfully."); document.setState(new PublishedState()); // 状态转换 } @Override public String getStateName() { return "Approved (Ready to Publish)"; } } class PublishedState implements DocumentState { @Override public void handleSubmit(DocumentContext document) { System.out.println("Error: Document is already published. Cannot submit again."); } @Override public void handleApprove(DocumentContext document) { System.out.println("Error: Document is already published."); } @Override public void handleReject(DocumentContext document) { System.out.println("Error: Document is already published."); } @Override public void handlePublish(DocumentContext document) { System.out.println("Error: Document is already published."); } @Override public String getStateName() { return "Published"; } } // 3. Context (环境类) class DocumentContext { private DocumentState currentState; private String content; public DocumentContext(String content) { this.content = content; this.currentState = new DraftState(); // 初始状态为草稿 System.out.println("Document created. Initial state: " + currentState.getStateName()); } public void setState(DocumentState state) { this.currentState = state; System.out.println("Document state changed to: " + this.currentState.getStateName()); } public DocumentState getCurrentState() { return currentState; } public String getContent() { return content; } public void setContent(String content) { this.content = content; System.out.println("Document content updated."); } // 将请求委托给当前状态对象 public void submit() { currentState.handleSubmit(this); } public void approve() { currentState.handleApprove(this); } public void reject() { currentState.handleReject(this); } public void publish() { currentState.handlePublish(this); } } // 客户端代码 public class StatePatternDemo { public static void main(String[] args) { DocumentContext doc = new DocumentContext("My awesome article content."); System.out.println("Current Document State: " + doc.getCurrentState().getStateName()); System.out.println("\n--- Trying to publish draft ---"); doc.publish(); // Error: Document must be approved before publishing. System.out.println("\n--- Submitting document ---"); doc.submit(); // Document submitted for moderation. State changed to: Moderation System.out.println("\n--- Trying to submit again ---"); doc.submit(); // Error: Document is already under moderation. System.out.println("\n--- Moderator rejects ---"); doc.reject(); // Document rejected by moderator. Reverted to draft. State changed to: Draft System.out.println("\n--- User edits and submits again ---"); doc.setContent("My awesome article content - v2"); doc.submit(); // Document submitted for moderation. State changed to: Moderation System.out.println("\n--- Moderator approves ---"); doc.approve(); // Document approved by moderator. State changed to: Approved (Ready to Publish) System.out.println("\n--- Trying to approve again ---"); doc.approve(); // Error: Document is already approved. System.out.println("\n--- Publishing document ---"); doc.publish(); // Document published successfully. State changed to: Published System.out.println("\n--- Trying to publish again ---"); doc.publish(); // Error: Document is already published. } }
在这个例子中:
DocumentContext 是环境类,它维护文档的当前状态。
DocumentState 是状态接口,定义了不同状态下可以执行的操作。
DraftState, ModerationState, ApprovedState, PublishedState 是具体状态类,它们实现了特定状态下的行为和状态转换逻辑。
客户端通过调用 DocumentContext 的方法(如 submit(), approve())来与文档交互,而 DocumentContext 会将这些请求委托给其当前的 DocumentState 对象。
状态模式使得我们可以很容易地添加新的状态(比如 ArchivedState 归档状态)而不需要修改 DocumentContext 类,只需要创建新的状态类并调整状态转换即可。