以此回顾《设计模式之禅》及其他设计模式书籍、视频中的状态模式。
Allow an object to alter its behavior when its internal state changes. The object will appear to change its class. (当一个对象的内在状态改变时,允许它修改自己的行为。这个对象看起来像是改变了类)
状态模式有3个重要部分。(1)Context(Account)(2)State (3)Concrete State
Context(Account):Maintains an instance of a ConcreteState subclass that defines current state。持有一个定义了当前状态的实例,这个“当前状态”就是一个具体状态(ConcreteState),实现了State,是State的子集。
State:Defines an interface for encapsulating the behavior associated with a particular state of the context. 定义了一个接口,接口里包含了context持有的具体状态要实现的方法。
Concrete State:Each subclass implements a behavior associated with a state of context。具体状态里实现了接口中定义的方法。
理解了这3个部分,再看定义,不难发现,状态模式其实就是说,当context持有的某个状态(concrete state)改变时,它的行为也发生了改变(因为每个concrete state对state接口中定义的方法的实现不同,行为也就不同)。
例如,把电梯抽象出4个状态,分别是运行、停止、开门、关门,则它的状态模式UML类图为:
避免了switch...case或者if...else的使用,减少了程序的负责行,使逻辑清晰。试想,刚才的电梯的例子,假设不用状态模式,则会变成这样,直接上代码:
public void close() {
//电梯在什么情况下能关闭
switch(state) {
//如果此时门开着
case OPEN_DOOR_STATE:
//可以关门
this.closeDoor();
this.setState(CLOSE_DOOR_STATE);
break;
//如果此时门关着
case CLOSE_DOOR_STATE:
//do nothing
break;
//如果此时电梯正在运行
case RUN_STATE:
//do nothing
break;
//如果此时电梯停止运行
case STOP_STATE:
//do nothing
break;
}
}
//同理,开门
public void openDoor() {
//电梯什么情况下能开门
switch(state) {
case XXX:break;
case XXX:break;
case XXX:break;
case XXX:break;
}
}
//同理,运行
public void run() {
//电梯什么情况下能运行
switch(state) {
case XXX:break;
case XXX:break;
case XXX:break;
case XXX:break;
}
}
//同理,停止
public void stop() {
//电梯什么情况下能停止
switch(state) {
case XXX:break;
case XXX:break;
case XXX:break;
case XXX:break;
}
}
显然,大量的switch case语句这个类过长,程序逻辑结构复杂,不容易理解,难以维护。
状态模式很好的体现了开闭原则和单一职责原则,每个状态都是一个子类,只要增加子类,就能进行扩展,比如电梯的例子,如果想增加通电和断电状态,直接增加子类就可以实现,但是如果用上面的未使用状态模式的代码,就会又增加一大波判断,逻辑更加复杂,且破坏了开闭原则。
这也是状态模式的要求,将类的变换放置到类的内部来实现,外部的调用不知道类内部如何实现状态和行为的变换,符合迪米特法则(最少知道法则)。
用ATM机的例子(来自于视频:https://www.youtube.com/watch?v=MGEx35FjBuo)来讲一个状态模式的应用。
1、想想ATM机的所有可能状态。
(1)HasCard
(2)NoCard
(3)HasPin
(4)NoCash
2、想想人们使用ATM机的几种操作。
(1)InsertCard
(2)EjectCard
(3)InsertPin
(4)RequestCash
3、UML类图
4、具体代码
ATMState.java
public interface ATMState {
void insertCard();
void ejectCard();
void insertPin(int pinEntered);
void requestCash(int cashToWithdraw);
}
ATMMachine.java
public class ATMMachine {
ATMState hasCard;
ATMState noCard;
ATMState hasCorrectPin;
ATMState atmOutOfMoney;
ATMState atmState;
int cashInMachine = 2000;
boolean correctPinEntered = false;
public ATMMachine() {
hasCard = new HasCard(this);
noCard = new NoCard(this);
hasCorrectPin = new HasPin(this);
atmOutOfMoney = new NoCash(this);
atmState = noCard;
if (cashInMachine < 0) {
atmState = atmOutOfMoney;
}
}
void setAtmState(ATMState newAtmState) {
atmState = newAtmState;
}
public void setCashInMachine(int newCashInMachine) {
cashInMachine = newCashInMachine;
}
public void insertCard() {
atmState.insertCard();
}
public void ejectCard() {
atmState.ejectCard();
}
public void requestCash(int cashToWithdraw) {
atmState.requestCash(cashToWithdraw);
}
public void insertPin(int pinEntered) {
atmState.insertPin(pinEntered);
}
public ATMState getYesCardState() {
return hasCard;
}
public ATMState getNoCardState() {
return noCard;
}
public ATMState getHasCorrectPinState() {
return hasCorrectPin;
}
public ATMState getAtmOutOfMoneyState() {
return atmOutOfMoney;
}
}
HasCard.java
public class HasCard implements ATMState {
ATMMachine atmMachine;
public HasCard(ATMMachine atmMachine) {
this.atmMachine = atmMachine;
}
@Override
public void insertCard() {
System.out.println("You can't enter more than one card!");
}
@Override
public void ejectCard() {
System.out.print("Card ejected");
atmMachine.setAtmState(atmMachine.getNoCardState());
}
@Override
public void insertPin(int pinEntered) {
if (pinEntered == 1234) {
System.out.println("Correct Pin");
atmMachine.correctPinEntered = true;
atmMachine.setAtmState(atmMachine.getHasCorrectPinState());
} else {
System.out.println("Wrong Pin");
atmMachine.correctPinEntered = false;
System.out.println("Card ejected");
atmMachine.setAtmState(atmMachine.getNoCardState());
}
}
@Override
public void requestCash(int cashToWithdraw) {
System.out.println("Enter pin first");
}
}
NoCard.java
public class NoCard implements ATMState {
ATMMachine atmMachine;
public NoCard(ATMMachine atmMachine) {
this.atmMachine = atmMachine;
}
@Override
public void insertCard() {
System.out.println("Please enter a pin");
atmMachine.setAtmState(atmMachine.getYesCardState());
}
@Override
public void ejectCard() {
System.out.println("Enter a card first");
}
@Override
public void insertPin(int pinEntered) {
System.out.println("Enter a card first");
}
@Override
public void requestCash(int cashToWithdraw) {
System.out.println("Enter a card first");
}
}
HasPin.java
public class HasPin implements ATMState {
ATMMachine atmMachine;
public HasPin(ATMMachine atmMachine) {
this.atmMachine = atmMachine;
}
@Override
public void insertCard() {
System.out.println("You can't enter more than one card");
}
@Override
public void ejectCard() {
System.out.println("Card ejected");
atmMachine.setAtmState(atmMachine.getNoCardState());
}
@Override
public void insertPin(int pinEntered) {
System.out.println("Already entered pin");
}
@Override
public void requestCash(int cashToWithdraw) {
if (cashToWithdraw > atmMachine.cashInMachine) {
System.out.println("Don't have that cash");
System.out.println("Card ejected");
atmMachine.setAtmState(atmMachine.getNoCardState());
} else {
System.out.println(cashToWithdraw + " is provided by machine");
atmMachine.setCashInMachine(atmMachine.cashInMachine - cashToWithdraw);
System.out.println("Card ejected");
atmMachine.setAtmState(atmMachine.getNoCardState());
if (atmMachine.cashInMachine <= 0) {
atmMachine.setAtmState(atmMachine.getAtmOutOfMoneyState());
}
}
}
}
NoCash.java
public class NoCash implements ATMState {
ATMMachine atmMachine;
public NoCash(ATMMachine atmMachine) {
this.atmMachine = atmMachine;
}
@Override
public void insertCard() {
System.out.println("We don't have money");
}
@Override
public void ejectCard() {
System.out.println("We don't have money, you didn't enter a card");
}
@Override
public void insertPin(int pinEntered) {
System.out.println("We don't have money");
}
@Override
public void requestCash(int cashToWithdraw) {
System.out.println("We don't have money");
}
}
TestATMMachine.java
public class TestATMMachine {
public static void main(String[] args) {
ATMMachine atmMachine = new ATMMachine();
atmMachine.insertCard();
atmMachine.ejectCard();
atmMachine.insertCard();
atmMachine.insertPin(1234);
atmMachine.requestCash(2000);
atmMachine.requestCash(100);
atmMachine.insertCard();
atmMachine.insertPin(1234);
}
}
(1)状态模式适用于某个对象当他的状态改变时,行为也有有较大变化的时候,考虑使用状态模式,但是对象的状态最好不要超过5个,否则会出现类爆炸,不好管理。
(2)当程序中有大量的if else 或者 switch case导致逻辑结构不清晰时,可以考虑状态模式。