设计模式之状态模式
状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
来解析这个定义:
- “内部状态”表明对象内部有一个属性来表示状态。
- “内部状态改变时改变它的行为,对象看起来好像修改了它的类”说明状态改变后对象的行为发生了非常大的变化,不像是同一类的行为。
从目前的分析中似乎无法推导出状态模式的类图结构。
从实际的例子出来,来看看状态模式是如何演进而来。
1. 草稿(Draft) → 提交 → 待审批(PendingReview)
2. 待审批 → 批准 → 已发布(Published)
3. 待审批 → 拒绝 → 草稿
4. 已发布 → 撤回 → 草稿
从直觉出发,会使用条件语句实现需求逻辑。
public class Document {
private String state = "DRAFT"; // 初始状态为草稿
public void submit() {
if (state.equals("DRAFT")) {
state = "PENDING_REVIEW";
System.out.println("文档已提交,等待审批");
} else {
System.out.println("当前状态不能提交");
}
}
public void approve() {
if (state.equals("PENDING_REVIEW")) {
state = "PUBLISHED";
System.out.println("文档已批准并发布");
} else {
System.out.println("当前状态不能批准");
}
}
public void reject() {
if (state.equals("PENDING_REVIEW")) {
state = "DRAFT";
System.out.println("文档被拒绝,返回草稿状态");
} else {
System.out.println("当前状态不能拒绝");
}
}
public void retract() {
if (state.equals("PUBLISHED")) {
state = "DRAFT";
System.out.println("文档已撤回,返回草稿状态");
} else {
System.out.println("当前状态不能撤回");
}
}
}
当需要添加新状态需要时要修改所有方法,违法了开闭原则。而且随着状态增加,if-else会越来越复杂,转换逻辑分散在各个方法中,修改一个状态可能影响其他状态,难以维护。
很容易会想到,使用枚举方式进行改进。
public class Document {
public enum State {
DRAFT, PENDING_REVIEW, PUBLISHED
}
private State state = State.DRAFT;
public void submit() {
switch(state) {
case DRAFT:
state = State.PENDING_REVIEW;
System.out.println("文档已提交,等待审批");
break;
default:
System.out.println("当前状态不能提交");
}
}
public void approve() {
switch(state) {
case PENDING_REVIEW:
state = State.PUBLISHED;
System.out.println("文档已批准并发布");
break;
default:
System.out.println("当前状态不能批准");
}
}
// reject()和retract()方法类似...
}
当状态行为复杂时,枚举会变得臃肿,添加新状态仍需修改现有代码,所有状态的行为都在Document类中。
我们思考,能否让每个状态自己知道该做什么,而不是由一个中央控制器决定?
面向对象的基本原则告诉我们,将变化的维度抽象出来。既然行为随状态变化,就考虑将"状态"这个概念提升为一等公民,每个状态应该有自己独立的类,通过多态机制自动选择正确行为。
这时我们意识到,状态不只是简单的值(如枚举值),状态是有行为的对象,状态转换可以封装在状态对象内部。
状态对象定义行为接口,每个状态处理自己状态时的行为逻辑,而持有状态属性的下上文更换为持有状态对象。
定义状态接口。
public interface DocumentState {
void submit(Document document);
void approve(Document document);
void reject(Document document);
void retract(Document document);
}
具体状态类。
// 草稿状态
public class DraftState implements DocumentState {
@Override
public void submit(Document document) {
System.out.println("文档已提交,等待审批");
document.setState(new PendingReviewState());
}
@Override
public void approve(Document document) {
System.out.println("草稿状态不能直接批准");
}
// 其他方法实现类似...
}
// 待审批状态
public class PendingReviewState implements DocumentState {
@Override
public void approve(Document document) {
System.out.println("文档已批准并发布");
document.setState(new PublishedState());
}
@Override
public void reject(Document document) {
System.out.println("文档被拒绝,返回草稿状态");
document.setState(new DraftState());
}
// 其他方法实现...
}
// 已发布状态
public class PublishedState implements DocumentState {
@Override
public void retract(Document document) {
System.out.println("文档已撤回,返回草稿状态");
document.setState(new DraftState());
}
// 其他方法实现...
}
上下文类。
public class Document {
private DocumentState state;
public Document() {
this.state = new DraftState(); // 初始状态
}
public void setState(DocumentState state) {
this.state = state;
}
public void submit() {
state.submit(this);
}
public void approve() {
state.approve(this);
}
public void reject() {
state.reject(this);
}
public void retract() {
state.retract(this);
}
}
改为状态模式实现后,添加新状态只需添加新类,无需修改现有代码,消除条件语句,状态判断由多态机制处理,每个状态类只关心自己的转换逻辑,状态行为局部化,修改一个状态不影响其他,轻松添加新状态和转换。
