定义
状态模式:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
具体来说就是:因为这个模式将状态封装成为独立的类,并将动作委托到代表当前状态的对象,我们知道行为会随着内部状态而改变。
从客户的视角来看:如果说你使用的对象能够完全改变它的行为,那么你会觉得,这个对象实际上是从别的类实例化而来的。然而,实际上,你知道我们是在使用组合通过简单应用不同的状态对象来造成类改变的假象。
生活例子
现在就来用一个生活上的例子来说明,有一个糖果机,有4种状态(没投币,投了币,售出糖果,糖果售罄),有4种操作(投入钱币,退回钱币,转动曲柄,发放糖果)。
那这太好做了,我们只需用 if 语句判断就行了:
public class GumballMachine {
final static int SOLD_OUT = 0;
final static int NO_QUARTER = 1;
final static int HAS_QUARTER = 2;
final static int SOLD = 3;
int state = SOLD_OUT;
int count = 0;
public GumballMachine(int count) {
this.count = count;
if (count > 0) {
state = NO_QUARTER;
}
}
public void insertQuarter() {
if (state == HAS_QUARTER) {
System.out.println("You can't insert another quarter");
} else if (state == NO_QUARTER) {
state = HAS_QUARTER;
System.out.println("You inserted a quarter");
} else if (state == SOLD_OUT) {
System.out.println("You can't insert a quarter, the machine is sold out");
} else if (state == SOLD) {
System.out.println("Please wait, we're already giving you a gumball");
}
}
public void ejectQuarter() {
if (state == HAS_QUARTER) {
System.out.println("Quarter returned");
state = NO_QUARTER;
} else if (state == NO_QUARTER) {
System.out.println("You haven't inserted a quarter");
} else if (state == SOLD) {
System.out.println("Sorry, you already turned the crank");
} else if (state == SOLD_OUT) {
System.out.println("You can't eject, you haven't inserted a quarter yet");
}
}
public void turnCrank() {
if (state == SOLD) {
System.out.println("Turning twice doesn't get you another gumball!");
} else if (state == NO_QUARTER) {
System.out.println("You turned but there's no quarter");
} else if (state == SOLD_OUT) {
System.out.println("You turned, but there are no gumballs");
} else if (state == HAS_QUARTER) {
System.out.println("You turned...");
state = SOLD;
dispense();
}
}
public void dispense() {
if (state == SOLD) {
System.out.println("A gumball comes rolling out the slot");
count = count - 1;
if (count == 0) {
System.out.println("Oops, out of gumballs!");
state = SOLD_OUT;
} else {
state = NO_QUARTER;
}
} else if (state == NO_QUARTER) {
System.out.println("You need to pay first");
} else if (state == SOLD_OUT) {
System.out.println("No gumball dispensed");
} else if (state == HAS_QUARTER) {
System.out.println("No gumball dispensed");
}
}
public void refill(int numGumBalls) {
this.count = numGumBalls;
state = NO_QUARTER;
}
public String toString() {
StringBuffer result = new StringBuffer();
result.append("\nMighty Gumball, Inc.");
result.append("\nJava-enabled Standing Gumball Model #2004\n");
result.append("Inventory: " + count + " gumball");
if (count != 1) {
result.append("s");
}
result.append("\nMachine is ");
if (state == SOLD_OUT) {
result.append("sold out");
} else if (state == NO_QUARTER) {
result.append("waiting for quarter");
} else if (state == HAS_QUARTER) {
result.append("waiting for turn of crank");
} else if (state == SOLD) {
result.append("delivering a gumball");
}
result.append("\n");
return result.toString();
}
}
这种设计是使用思虑周密的方法学实现的,如果读者是一个有经验的设计师,相信可以看出这种设计的缺陷,没错,就是写得太硬了,不灵活,如果我们想在糖果机加入一些新花样(或者说一些新的状态),那就需要改很多代码了。
我们有一个设计原则,叫“封装变化”原则,所以我们可以将经常变化的状态封装成不同的类,那就可以应付需求变化了。
我们可以定义一个状态接口:
public interface State {
public void insertQuarter();
public void ejectQuarter();
public void turnCrank();
public void dispense();
}
如何让所有状态类实现它:
public class NoQuarterState implements State {
GumballMachine gumballMachine;
public NoQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void insertQuarter() {
System.out.println("You inserted a quarter");
gumballMachine.setState(gumballMachine.getHasQuarterState());
}
public void ejectQuarter() {
System.out.println("You haven't inserted a quarter");
}
public void turnCrank() {
System.out.println("You turned, but there's no quarter");
}
public void dispense() {
System.out.println("You need to pay first");
}
public String toString() {
return "waiting for quarter";
}
}
public class HasQuarterState implements State {
GumballMachine gumballMachine;
public HasQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void insertQuarter() {
System.out.println("You can't insert another quarter");
}
public void ejectQuarter() {
System.out.println("Quarter returned");
gumballMachine.setState(gumballMachine.getNoQuarterState());
}
public void turnCrank() {
System.out.println("You turned...");
gumballMachine.setState(gumballMachine.getSoldState());
}
public void dispense() {
System.out.println("No gumball dispensed");
}
public String toString() {
return "waiting for turn of crank";
}
}
其他状态类请自行实现。
那么我们的糖果机类直接调用状态类,里面的方法也是直接调用状态类的方法,状态的改变由状态类来决定:
public class GumballMachine {
State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;
State state = soldOutState;
int count = 0;
public GumballMachine(int numberGumballs) {
soldOutState = new SoldOutState(this);
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
soldState = new SoldState(this);
this.count = numberGumballs;
if (numberGumballs > 0) {
state = noQuarterState;
}
}
public void insertQuarter() {
state.insertQuarter();
}
public void ejectQuarter() {
state.ejectQuarter();
}
public void turnCrank() {
state.turnCrank();
state.dispense();
}
void setState(State state) {
this.state = state;
}
void releaseBall() {
System.out.println("A gumball comes rolling out the slot...");
if (count != 0) {
count = count - 1;
}
}
int getCount() {
return count;
}
void refill(int count) {
this.count = count;
state = noQuarterState;
}
public State getState() {
return state;
}
public State getSoldOutState() {
return soldOutState;
}
public State getNoQuarterState() {
return noQuarterState;
}
public State getHasQuarterState() {
return hasQuarterState;
}
public State getSoldState() {
return soldState;
}
public String toString() {
StringBuffer result = new StringBuffer();
result.append("\nMighty Gumball, Inc.");
result.append("\nJava-enabled Standing Gumball Model #2004");
result.append("\nInventory: " + count + " gumball");
if (count != 1) {
result.append("s");
}
result.append("\n");
result.append("Machine is " + state + "\n");
return result.toString();
}
}
这样我们就可以动态改变不同的状态类来实现改变状态了,而且需要什么新的状态,也可以不用修改原来的状态了,直接增加一个新的状态就行了。
与策略模式比较
如果你看过我的
另一篇博文:策略模式,你可能就会发现,状态模式和策略模式怎么那么像,都是:允许对象能够通过组合和委托来拥有不同的行为或算法。
对,如果你发现了这一点,你就大概知道它们的使用方法了,但还不够,因为它们的意图和要代替的方案不同。
策略模式是:通常会用行为或算法来配置主类。并不鼓励策略类来控制策略转换,而是由主类来控制。(这样策略类之间没依赖关系,更有弹性),但对于不同的主类来说,通常只有一个最适合的策略对象(如:绿头鸭就被设置成利用典型的飞翔行为进行飞翔;如果是橡皮鸭使用的飞翔行为只能让它紧贴地面)。简而言之,策略模式的意图是:不同的主类使用一种不同的但最适合的策略对象。策略模式可以想成是除了继承之外的一种弹性代替方案。
状态模式是:允许主类(即上面的糖果机类)随着状态的改变而改变行为。状态可由主类或状态类来控制。(如果有状态类来控制,状态类之间可能有依赖关系)。简而言之,状态模式的意图是:一个主类有多个状态对象,通过改变不同的状态对象来改变自己的行为。状态模式可以想成是不用在主类中放置许多条件判断的代替方案。
所以对于这两个模式,只能说:结构上很像,意图和要代替的方案却十分不同。
欢迎在下面评论交流。