Java设计模式之状态模式详解
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;}@Overridepublic void insertCoin() {System.out.println("You inserted a quarter");vendingMachine.setState(vendingMachine.getHasQuarterState());}@Overridepublic void ejectCoin() {System.out.println("You haven't inserted a quarter yet");}@Overridepublic void turnCrank() {System.out.println("You need to insert a quarter first");}@Overridepublic 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;}@Overridepublic void insertCoin() {System.out.println("You already inserted a quarter");}@Overridepublic void ejectCoin() {System.out.println("Quarter returned");vendingMachine.setState(vendingMachine.getNoQuarterState());}@Overridepublic void turnCrank() {System.out.println("You turned...");vendingMachine.setState(vendingMachine.getSoldState());vendingMachine.dispense();}@Overridepublic void dispense() {System.out.println("No item dispensed");}
}
SoldState(售出商品状态)
public class SoldState implements State {private VendingMachine vendingMachine;public SoldState(VendingMachine vendingMachine) {this.vendingMachine = vendingMachine;}@Overridepublic void insertCoin() {System.out.println("Please wait, we're already giving you an item");}@Overridepublic void ejectCoin() {System.out.println("Sorry, you've already turned the crank");}@Overridepublic void turnCrank() {System.out.println("Turning twice doesn't get you another item!");}@Overridepublic 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;}@Overridepublic void insertCoin() {System.out.println("You can't insert a quarter, the machine is out of stock");}@Overridepublic void ejectCoin() {System.out.println("You can't eject, you haven't inserted a quarter yet");}@Overridepublic void turnCrank() {System.out.println("You turned, but there are no items");}@Overridepublic 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
),使代码结构更加清晰,易于理解和维护。 - 扩展性强:当需要增加新的状态时,只需要新增一个具体状态类并实现状态接口即可,不需要修改上下文类和其他状态类的代码,符合开闭原则。
- 可维护性高:由于每个状态类都专注于处理一种状态的行为,修改某个状态的行为逻辑时,只需要修改对应的状态类,不会影响其他状态的逻辑,降低了代码的耦合度。
缺点
- 类数量增加:每个具体状态都需要一个独立的类来实现,当状态较多时,会导致类的数量急剧增加,增加了系统的复杂性和维护成本。
- 状态转换的复杂性:在某些情况下,状态之间的转换规则可能比较复杂,需要仔细处理状态之间的转换逻辑,否则可能会出现状态混乱的问题。
五、应用场景
- 工作流系统:在工作流系统中,任务可能处于不同的状态,如“待处理”“处理中”“已完成”“已驳回”等,不同状态下对任务的操作(如提交、审批、退回等)逻辑不同,使用状态模式可以很好地管理这些状态和对应的操作。
- 游戏开发:游戏角色、游戏场景等往往存在多种状态,例如游戏角色的攻击、防御、跳跃、奔跑等状态,每个状态下的行为和动画效果不同,状态模式可以有效组织这些状态相关的逻辑。
- 设备状态管理:像打印机、路由器等设备,会有“就绪”“打印中”“故障”“离线”等状态,不同状态下对设备的操作响应不同,通过状态模式可以清晰地实现设备的状态管理和行为控制。