狀態模式

定義

狀態模式允許對象在內部狀態改變時改變它的行爲,對象看起來好像修改了它的類。

這個模式將狀態封裝成爲獨立的類,並將動作委託到代表當前狀態的對象,行爲會隨着內部狀態而改變。

在這裏插入圖片描述

場景描述

糖果機就跟街上那個卡一元錢後,小蘋果能轉出來小球很類似,對於糖果機來說,一共有四種狀態,分別是沒有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--;  
        }  
    }  
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章