Java设计模式之状态模式详解
在软件开发过程中,我们经常会遇到这样的情况:一个对象的行为会根据其内部状态的变化而变化。例如,电梯可能处于运行、停止、维修等不同状态,不同状态下对按钮操作的响应不同;游戏角色在正常、受伤、死亡等状态下,其动作和交互方式也有差异。对于这类问题,状态模式(State Pattern)提供了优雅的解决方案。下面将通过图文结合的方式,详细介绍Java中的状态模式。
一、状态模式概念
状态模式是一种行为型设计模式,它允许一个对象在其内部状态改变时改变它的行为,使对象看起来似乎修改了它的类。在状态模式中,将对象的每个状态都封装成一个独立的类,这些类实现同一个状态接口,而拥有状态的对象仅需在内部维护一个当前状态对象的引用。当对象的状态发生变化时,只需要替换当前的状态对象,对象的行为就会随之改变,从而避免了大量的条件判断语句,使代码更加清晰、易于维护和扩展。
二、状态模式结构(Mermaid类图)
使用Mermaid绘制状态模式的类图,能直观呈现各角色关系。
在上述类图中:
- Context(上下文):维护一个对State对象的引用,代表当前状态,同时提供对外的操作接口,这些操作会委托给当前的State对象来处理。
- State(状态接口):定义了一个接口,用于封装与Context的一个状态相关的行为,所有具体状态类都要实现这个接口。
- ConcreteState(具体状态类):实现State接口,每个具体状态类都对应Context的一个具体状态,在具体状态类中实现与该状态相关的行为逻辑。
三、Java代码示例
以自动售货机为例,售货机有“有商品”“无商品”“售出商品”等状态,不同状态下投币、退币、购买商品的操作逻辑不同。下面通过Java代码实现这个自动售货机的状态模式。
1. 定义状态接口State
public interface State {
void insertCoin();
void ejectCoin();
void turnCrank();
void dispense();
}
2. 实现具体状态类
NoQuarterState(无硬币状态)
public class NoQuarterState implements State {
private VendingMachine vendingMachine;
public NoQuarterState(VendingMachine vendingMachine) {
this.vendingMachine = vendingMachine;
}
@Override
public void insertCoin() {
System.out.println("You inserted a quarter");
vendingMachine.setState(vendingMachine.getHasQuarterState());
}
@Override
public void ejectCoin() {
System.out.println("You haven't inserted a quarter yet");
}
@Override
public void turnCrank() {
System.out.println("You need to insert a quarter first");
}
@Override
public void dispense() {
System.out.println("You need to pay first");
}
}
HasQuarterState(有硬币状态)
public class HasQuarterState implements State {
private VendingMachine vendingMachine;
public HasQuarterState(VendingMachine vendingMachine) {
this.vendingMachine = vendingMachine;
}
@Override
public void insertCoin() {
System.out.println("You already inserted a quarter");
}
@Override
public void ejectCoin() {
System.out.println("Quarter returned");
vendingMachine.setState(vendingMachine.getNoQuarterState());
}
@Override
public void turnCrank() {
System.out.println("You turned...");
vendingMachine.setState(vendingMachine.getSoldState());
vendingMachine.dispense();
}
@Override
public void dispense() {
System.out.println("No item dispensed");
}
}
SoldState(售出商品状态)
public class SoldState implements State {
private VendingMachine vendingMachine;
public SoldState(VendingMachine vendingMachine) {
this.vendingMachine = vendingMachine;
}
@Override
public void insertCoin() {
System.out.println("Please wait, we're already giving you an item");
}
@Override
public void ejectCoin() {
System.out.println("Sorry, you've already turned the crank");
}
@Override
public void turnCrank() {
System.out.println("Turning twice doesn't get you another item!");
}
@Override
public void dispense() {
vendingMachine.releaseItem();
if (vendingMachine.getCount() > 0) {
vendingMachine.setState(vendingMachine.getNoQuarterState());
} else {
System.out.println("Oops, out of stock!");
vendingMachine.setState(vendingMachine.getOutOfStockState());
}
}
}
OutOfStockState(无商品状态)
public class OutOfStockState implements State {
private VendingMachine vendingMachine;
public OutOfStockState(VendingMachine vendingMachine) {
this.vendingMachine = vendingMachine;
}
@Override
public void insertCoin() {
System.out.println("You can't insert a quarter, the machine is out of stock");
}
@Override
public void ejectCoin() {
System.out.println("You can't eject, you haven't inserted a quarter yet");
}
@Override
public void turnCrank() {
System.out.println("You turned, but there are no items");
}
@Override
public void dispense() {
System.out.println("No item to dispense");
}
}
3. 定义上下文类VendingMachine
public class VendingMachine {
State noQuarterState;
State hasQuarterState;
State soldState;
State outOfStockState;
State state = noQuarterState;
int count = 0;
public VendingMachine(int count) {
this.count = count;
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
soldState = new SoldState(this);
outOfStockState = new OutOfStockState(this);
}
public void insertCoin() {
state.insertCoin();
}
public void ejectCoin() {
state.ejectCoin();
}
public void turnCrank() {
state.turnCrank();
state.dispense();
}
void setState(State state) {
this.state = state;
}
void releaseItem() {
System.out.println("A gumball comes rolling out the slot...");
if (count > 0) {
count = count - 1;
}
}
public State getNoQuarterState() {
return noQuarterState;
}
public State getHasQuarterState() {
return hasQuarterState;
}
public State getSoldState() {
return soldState;
}
public State getOutOfStockState() {
return outOfStockState;
}
public int getCount() {
return count;
}
}
4. 测试代码
public class VendingMachineTest {
public static void main(String[] args) {
VendingMachine vendingMachine = new VendingMachine(10);
vendingMachine.insertCoin();
vendingMachine.turnCrank();
vendingMachine.ejectCoin();
vendingMachine.insertCoin();
vendingMachine.insertCoin();
vendingMachine.turnCrank();
for (int i = 0; i < 11; i++) {
vendingMachine.turnCrank();
}
}
}
在上述代码中:
State
接口定义了自动售货机在不同状态下可执行的操作方法。- 各个具体状态类(如
NoQuarterState
、HasQuarterState
等)实现State
接口,在其中编写对应状态下的操作逻辑,并在适当时候切换售货机的状态。 VendingMachine
类作为上下文,持有所有具体状态类的实例,对外提供操作接口,将操作委托给当前状态对象处理,同时可以根据业务逻辑切换状态。VendingMachineTest
类用于测试自动售货机在不同操作下的状态变化和行为表现。
四、状态模式的优缺点
优点
- 清晰的逻辑分离:将不同状态下的行为逻辑封装在独立的状态类中,避免了大量的条件判断语句(如
if-else
或switch-case
),使代码结构更加清晰,易于理解和维护。 - 扩展性强:当需要增加新的状态时,只需要新增一个具体状态类并实现状态接口即可,不需要修改上下文类和其他状态类的代码,符合开闭原则。
- 可维护性高:由于每个状态类都专注于处理一种状态的行为,修改某个状态的行为逻辑时,只需要修改对应的状态类,不会影响其他状态的逻辑,降低了代码的耦合度。
缺点
- 类数量增加:每个具体状态都需要一个独立的类来实现,当状态较多时,会导致类的数量急剧增加,增加了系统的复杂性和维护成本。
- 状态转换的复杂性:在某些情况下,状态之间的转换规则可能比较复杂,需要仔细处理状态之间的转换逻辑,否则可能会出现状态混乱的问题。
五、应用场景
- 工作流系统:在工作流系统中,任务可能处于不同的状态,如“待处理”“处理中”“已完成”“已驳回”等,不同状态下对任务的操作(如提交、审批、退回等)逻辑不同,使用状态模式可以很好地管理这些状态和对应的操作。
- 游戏开发:游戏角色、游戏场景等往往存在多种状态,例如游戏角色的攻击、防御、跳跃、奔跑等状态,每个状态下的行为和动画效果不同,状态模式可以有效组织这些状态相关的逻辑。
- 设备状态管理:像打印机、路由器等设备,会有“就绪”“打印中”“故障”“离线”等状态,不同状态下对设备的操作响应不同,通过状态模式可以清晰地实现设备的状态管理和行为控制。