为什么有状态模式?
在软件开发中,有些对象的行为取决于其内部状态,而且这些状态可能在运行时发生变化。如果直接在对象内部使用大量的条件语句来管理状态转换,会导致代码变得复杂、难以维护。状态模式就是为了解决这一问题而诞生的。
在Java中,状态模式通常包含以下角色:
- 环境类(Context):
定义了客户端需要的接口,并且维护了一个指向当前状态的引用。
负责调用当前状态下对应的方法,并根据业务需求切换状态对象。 - 抽象状态(State):
是所有具体状态类的基类或者接口,定义了所有可能的状态共有的方法以及与环境类交互的接口。 - 具体状态(Concrete State):
每个具体状态类实现抽象状态定义的行为,这些类代表对象的不同状态,并可以决定何时以及如何进行状态转换。
代码示例
在一个ATM机系统中,环境类可能是ATM机器本身,而状态类可以是"取款中"、"存款中"、"查询余额中"等具体状态。每种状态下ATM的行为不同,如"取款中"状态下可以执行取款操作和退卡操作,而在"查询余额中"状态下只能查询余额。
java
// 抽象状态
abstract class State {
protected ATM atm; // ATM环境
public void setATM(ATM atm) {
this.atm = atm;
}
// 抽象方法,由具体状态来实现
public abstract void insertCard();
public abstract void ejectCard();
public abstract void withdrawMoney(int amount);
}
具体状态:
java
// 具体状态
class NoCardState extends State {
@Override
public void insertCard() {
System.out.println("卡片已插入,状态变为有卡状态");
atm.setCurrentState(atm.getHasCardState()); // 状态切换
}
// ... 实现其他方法
}
// 其他具体状态...
class HasCardState extends State {
@Override
public void ejectCard() {
System.out.println("卡片已退出,状态变为无卡状态");
atm.setCurrentState(atm.getNoCardState()); // 状态切换
}
@Override
public void withdrawMoney(int amount) {
if (amount <= atm.getBalance()) {
atm.setBalance(atm.getBalance() - amount);
System.out.println("取款成功,金额为: " + amount);
} else {
System.out.println("余额不足,无法取款");
}
}
// ... 其他方法实现
}
ATM环境类(上下文):
java
class ATM {
private int balance;
private State currentState;
public ATM() {
currentState = new NoCardState(); // 初始化状态
currentState.setATM(this);
}
// 获取不同状态实例
private NoCardState getNoCardState() { /*...*/ }
private HasCardState getHasCardState() { /*...*/ }
// 状态变更方法
public void setCurrentState(State state) {
currentState = state;
currentState.setATM(this);
}
// ... 其他相关方法,如获取
jdk中的状态模式
状态模式允许对象在其内部状态改变时改变其行为,使得对象看起来像是改变了它的类。
- java.util.Iterator 接口以及其实现类在遍历集合元素时就体现了一种状态变化的过程。iterator的不同方法(如hasNext()、next()、remove()等)对应了迭代器的不同状态,并根据当前状态决定行为。
- javax.faces.lifecycle.LifeCycle#execute() 在JSF(JavaServer Faces)框架中,生命周期管理就是一个典型的状态机应用,每个阶段(Restore View、Apply Request Values、Process Validations 等)代表一个状态,当执行到不同的阶段时,行为会有所不同。
- Swing/AWT GUI Toolkit中的组件状态 诸如按钮、菜单等GUI组件,在处理鼠标点击、键盘输入等事件时,其内部可能会使用状态模式来维护不同状态下的表现和行为。
具体业务代码实现分析
在使用状态模式实现订单状态管理时,可以为每个订单状态定义一个独立的状态类。例如:
java
// 抽象状态接口或抽象类
public abstract class OrderState {
protected Order order;
public void setOrder(Order order) {
this.order = order;
}
// 定义每个状态下通用的方法,如获取状态名等
public abstract String getStateName();
// 状态转换方法,由具体状态类实现
public abstract void pay();
}
具体状态类 - 待支付状态:
java
public class PendingPaymentState extends OrderState {
@Override
public String getStateName() {
return "待支付";
}
@Override
public void pay() {
// 检查支付条件,处理支付逻辑(调用第三方支付API等)
// 支付成功后切换状态
if (paymentSuccessful()) {
order.setState(new PaidState());
// 更新数据库或其他操作...
}
}
private boolean paymentSuccessful() {
// 实现支付成功的验证逻辑
// ...
return true; // 假设支付成功
}
}
具体状态类 - 已支付状态:
java
public class PaidState extends OrderState {
@Override
public String getStateName() {
return "已支付";
}
@Override
public void pay() {
// 在已支付状态下再次支付通常不需要处理,或者抛出异常表示状态错误
throw new IllegalStateException("订单已支付,无法再次支付");
}
}
订单实体类:
java
public class Order {
private OrderState state;
// 构造函数初始化状态
public Order() {
this.state = new PendingPaymentState();
state.setOrder(this);
}
public void setState(OrderState state) {
this.state = state;
state.setOrder(this);
}
public void pay() {
// 调用当前状态下的支付行为
state.pay();
}
// 其他与订单相关的业务方法...
}
当调用order.pay()方法时,实际执行的操作会根据当前订单状态的不同而不同。当订单从"待支付"状态变为"已支付"状态时,其行为规则也随之改变。
状态模式优缺点
优点:
- 职责分离:将不同状态下的行为划分到不同的类中,遵循了"单一职责原则",让代码结构更清晰,易于理解和维护。
- 扩展性好:新的状态可以通过添加新的状态类轻松实现,而无需修改原有代码。
- 减少条件判断:将状态相关的逻辑分散到各个状态类中,可以避免在上下文对象中使用复杂的条件分支语句来决定行为。
- 封装状态转换规则:状态之间的转换逻辑被封装在状态类中,降低了对象间的耦合度,使状态变化更为透明和明确。
- 易于理解与调试:每个状态类专注于自身的处理逻辑,便于测试和调试。
缺点: - 增加类的数量:随着系统状态数量的增长,状态类的数量也会相应增加,可能导致类爆炸问题,从而增加了系统的复杂性和管理难度。
- 状态转换复杂时难以维护:如果状态转换关系非常复杂或者有大量状态之间相互转换的情况,可能会导致状态类之间的耦合度增加,使得状态模式变得难以维护。
- 上下文需要维护状态对象:上下文对象必须持有当前状态对象的引用,并负责状态之间的切换,这会增加上下文类的复杂性。