定义
状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
这个模式将状态封装成为独立的类,并将动作委托到代表当前状态的对象,行为会随着内部状态而改变。
场景描述
糖果机就跟街上那个卡一元钱后,小苹果能转出来小球很类似,对于糖果机来说,一共有四种状态,分别是没有25分钱的状态,有25分钱状态,有糖果状态,没糖果状态,其中1和2、3和4状态是互斥的。
没引入设计模式之前的代码一般这样写:
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;
}
}
// 当有25分钱投进来,就会执行这里
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");
}
}
// 现在,如果顾客试着退回25分钱
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();
}
}
// 调用此方法,发放糖果
private 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("Mighty Gumball, Inc.");
result.append("Java-enabled Standing Gumball Model");
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();
}
}
变化
客户要求当曲柄转动时,有10%的几率掉下来的是两颗糖果(多送你一颗),对着上面的代码不妨停下来想一想。
按照Head First的剧情是增加一个新的状态,赢家状态。之后在上面的每个方法里面加上赢家的判断,更糟糕的是转动曲柄方法,因为首先要检查这个顾客是不是赢家。
发放糖果的时候假如剩最后一颗糖果,是不是就不能随机赢家了?
这种改法明显改的太多,不利于维护。
正解
新的代码,其中的精髓我觉的就在于,所有的状态都继承相同的接口,这样状态相互切换,没有任何影响,也就是抽象成同类的事物,不同的执行内容。例如在HasQuarterState状态,调用turnCrank()方法,在方法体里就设置Context到WinnerState状态,接下来Context执行request【开篇的类图】操作时,就又调的是WinnerState状态了。
public interface State
{
void insertQuater();//插入25分钱
void ejectQuater(); //退回25分钱
void turnCrank(); //转动曲柄
void dispense(); //弹出糖果
}
public class WinnerState implements State {
GumballMachine gumballMachine;
public WinnerState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void insertQuarter() {
System.out.println("Please wait, we're already giving you a Gumball");
}
public void ejectQuarter() {
System.out.println("Please wait, we're already giving you a Gumball");
}
public void turnCrank() {
System.out.println("Turning again doesn't get you another gumball!");
}
// 我们在这里释放出两颗糖果,让你和进入NoQuarterState或SoldOutState
public void dispense() {
gumballMachine.releaseBall();
if (gumballMachine.getCount() == 0) {
gumballMachine.setState(gumballMachine.getSoldOutState());
} else {
gumballMachine.releaseBall();
System.out.println("YOU'RE A WINNER! You got two gumballs for your quarter");
if (gumballMachine.getCount() > 0) {
gumballMachine.setState(gumballMachine.getNoQuarterState());
} else {
System.out.println("Oops, out of gumballs!");
gumballMachine.setState(gumballMachine.getSoldOutState());
}
}
}
public void refill() { }
public String toString() {
return "despensing two gumballs for your quarter, because YOU'RE A WINNER!";
}
}
public class HasQuarterState implements State {
// 首先,我们增加一个随机数产生器,产生10%赢的机会
Random randomWinner = new Random(System.currentTimeMillis());
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...");
int winner = randomWinner.nextInt(10);
if ((winner == 0) && (gumballMachine.getCount() > 1)) {
gumballMachine.setState(gumballMachine.getWinnerState());
} else {
gumballMachine.setState(gumballMachine.getSoldState());
}
}
public void dispense() {
System.out.println("No gumball dispensed");
}
}
public class GumballMachineTestDrive {
public static void main(String[] args) {
GumballMachine gumballMachine =
new GumballMachine(10);
System.out.println(gumballMachine);
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine);
// 多重复几次,就可以看是否中奖了
}
}
public class GumballMachine {
//状态实例
State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;
State winnerState;
// 实例变量state,初始化为糖果售罄状态
State state = soldOutState;
// 记录机器内装有糖果的数目,开始机器是没有装糖果的
int count=0;
// 构造器取得糖果的初始数目并把它放在一个实例变量count中
public GumballMachine(int numberGumballs) {
// 每种状态都创建一个状态实例
soldOutState=new SoldOutState(this);
noQuarterState=new NoQuarterState(this);
hasQuarterState=new HasQuarterState(this);
soldState=new SoldState(this);
winnerState = new WinnerState(this);
this.count = numberGumballs;
// 若超过0颗糖果,就将状态设置为NoQuarterState
if(numberGumballs > 0) {
state = noQuarterState;
}
}
// 取得机器内的糖果数目
public int getCount() {
return count;
}
// 取得糖果售罄状态
// ……
// 投入25分钱
public void insertQuarter(){
state.insertQuarter();
}
// 拒绝25分钱
public void ejectQuarter(){
state.ejectQuarter();
}
// 转动曲柄
public void turnCrank(){
state.turnCrank();
state.dispense();
}
// 设置状态
public void setState(State state){
this.state=state;
}
// 糖果滚出来一个
public void releaseBall(){
System.out.println("A gumball comes rolling out of the solt...");
if(count!=0){
count--;
}
}
}