場景描述:我們想要寫一個糖果機,如下是它的狀態圖。
我們想到最直接的解決辦法就是,大段的if else塊
但是正如我們之前所說,if else塊一旦遇到需求變更等情況,不僅面臨着大量代碼的重寫等,還面臨着出現bug的情況。並且這種設計不符合我們之前提到的開放-關閉設計原則(對擴展開放,對修改關閉)。
比如我們突然要加一條隨機幸運兒,有10%的機率一次獲得兩個糖豆。
所以我們嘗試換一種方法進行實現。首先我們寫一個表示狀態的接口
public interface State {
void insertQuarter();
void ejectQuarter();
void turnCrank();
void dispense();
}
接着實現幾種狀態,並在這些狀態類中定義了狀態轉換的行爲
public class NoQuarterState implements State {
GumballMachine gumballMachine;
public NoQuarterState(GumballMachine gumballMachine){
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("You inserted a quarter");
gumballMachine.setState(gumballMachine.getHasQuarterState());
}
@Override
public void ejectQuarter() {
System.out.println("You haven't inserted a quarter");
}
@Override
public void turnCrank() {
System.out.println("You turned, but there's no quarter");
}
@Override
public void dispense() {
System.out.println("You need to pay first");
}
}
public class HasQuarterState implements State {
GumballMachine gumballMachine;
Random randomWinner = new Random(System.currentTimeMillis());
public HasQuarterState(GumballMachine gumballMachine){
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("You can't insert another quarter");
}
@Override
public void ejectQuarter() {
System.out.println("Quarter returned");
gumballMachine.setState(gumballMachine.getNoQuarterState());
}
@Override
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());
}
}
@Override
public void dispense() {
System.out.println("No gumball dispensed");
}
}
public class SoldState implements State {
GumballMachine gumballMachine;
public SoldState(GumballMachine gumballMachine){
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("Please wait, we're already giving you a gumball");
}
@Override
public void ejectQuarter() {
System.out.println("Sorry, you already turned the crank");
}
@Override
public void turnCrank() {
System.out.println("Turning twice doesn't get you another gumball!");
}
@Override
public void dispense() {
gumballMachine.releaseBall();
if (gumballMachine.getCount() > 0){
gumballMachine.setState(gumballMachine.getNoQuarterState());
}else {
System.out.println("Oops, out of gumballs");
gumballMachine.setState(gumballMachine.getSoldOutState());
}
}
}
public class WinnerState implements State {
GumballMachine gumballMachine;
public WinnerState(GumballMachine gumballMachine){
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("Please wait, we're already giving you a gumball");
}
@Override
public void ejectQuarter() {
System.out.println("Sorry, you already turned the crank");
}
@Override
public void turnCrank() {
System.out.println("Turning twice doesn't get you another gumball!");
}
@Override
public void dispense() {
System.out.println("YOU'RE A WINNER! You get two gumballs for your quarter");
gumballMachine.releaseBall();
if (gumballMachine.getCount() == 0){
gumballMachine.setState(gumballMachine.getSoldOutState());
}else {
gumballMachine.releaseBall();
if (gumballMachine.getCount() > 0){
gumballMachine.setState(gumballMachine.getNoQuarterState());
}else {
System.out.println("Oops, out of gumballs");
gumballMachine.setState(gumballMachine.getSoldOutState());
}
}
}
}
public class SoldOutState implements State {
GumballMachine gumballMachine;
public SoldOutState(GumballMachine gumballMachine){
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("You can't insert a quarter, the machine is sold out");
}
@Override
public void ejectQuarter() {
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 gumballs");
}
@Override
public void dispense() {
System.out.println("No gumball dispensed");
}
}
然後實現糖果機的代碼
public class GumballMachine {
State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;
State winnerState;
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);
winnerState = new WinnerState(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 -= 1;
}
}
void refill(int count){
this.count = count;
state = noQuarterState;
}
public State getSoldOutState() {
return soldOutState;
}
public State getNoQuarterState() {
return noQuarterState;
}
public State getHasQuarterState() {
return hasQuarterState;
}
public State getSoldState() {
return soldState;
}
public State getWinnerState() {
return winnerState;
}
public State getState() {
return state;
}
public int getCount() {
return count;
}
public String toString(){
return "Mighty Gumball, Inc.\nJava-enabled Standing Gumball Model #2004\nInventory: " + count + " gumballs\nMachine is " + getState() + "\n";
}
}
最後測試一下
public class GumballMachineTestDrive {
public static void main(String[] args) {
GumballMachine gumballMachine = new GumballMachine(5);
System.out.println(gumballMachine);
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine);
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine);
}
}
從而引出了我們的主角,狀態模式。
狀態模式允許對象在內部狀態改變時改變它的行爲,對象看起來好像修改了它的類。
因爲這個模式將狀態封裝成爲獨立的類,並將動作委託到代表當前狀態的對象,我們知道行爲會隨着內部狀態而改變。
如果從客戶的視角來看:如果說你使用的對象能夠完全改變它的行爲,那麼你會覺得,這個對象實際上是從別的類實例化而來的。然而,實際上,你知道我們是在使用組合通過簡單引用不同的狀態對象來造成類改變的假象。
它的類圖如下
好像看起來和策略模式的類圖一模一樣。但是兩者在意圖上有着根本的不同。
以狀態模式而言,我們將一羣行爲封裝在狀態對象中,context的行爲隨時可委託到那些狀態對象中的一個。隨着時間的流逝,當前狀態在狀態對象集合中游走改變,以反映出context內部的狀態,因此,context的行爲也會跟着改變。但是context的客戶對於狀態對象瞭解不多,甚至根本是渾然不覺。
以策略模式而言,客戶通常主動指定Context所要組合的策略對象是哪一個。現在,固然策略模式讓我們具有彈性,能夠在運行時改變策略,但對於某個context對象來說,通常都只有一個最適當的策略對象。
一般來說,我們把策略模式想成是除了繼承之外的一種彈性替代方案。如果你使用繼承定義了一個類的行爲,你將被這個行爲困住,甚至要修改它都很難。有了策略模式,你可以通過組合不同的對象來改變行爲。
我們把狀態模式想成是不用在context中放置許多條件判斷的替代方案。通過將行爲包裝進狀態對象中,你可以通過在context內簡單地改變狀態對象來改變context的行爲。
總結: 當我們需要大量的條件判斷時,就可以考慮狀態模式是否適合了。