設(shè)計(jì)模式學(xué)習(xí)專欄十一--------狀態(tài)模式

設(shè)計(jì)模式學(xué)習(xí)專欄十一--------狀態(tài)模式

名稱: 狀態(tài)模式 (State)

價(jià)值觀念: 通過(guò)改變對(duì)象內(nèi)部的狀態(tài)來(lái)幫助對(duì)象控制自己的行為

場(chǎng)景


設(shè)計(jì)一個(gè)萬(wàn)能糖果機(jī) , 我們希望設(shè)計(jì)盡可能有彈性 , 而且將來(lái)我們可能要為它增加更多的行為~

image

剛開(kāi)始的設(shè)計(jì)方式

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");
        }
    }
    ......
}

我們的第一版設(shè)計(jì)完成了 , 發(fā)現(xiàn)每個(gè)動(dòng)作下都需要判斷當(dāng)前的狀態(tài),然后做出相應(yīng)的動(dòng)作.

新功能引入 , 我們加入了一個(gè)新的游戲狀態(tài) , 當(dāng)曲柄被轉(zhuǎn)動(dòng)時(shí),有10%的幾率掉下來(lái)的是兩顆糖果(贏家狀態(tài))

此時(shí)我們會(huì)發(fā)現(xiàn)第一版設(shè)計(jì)中. 每個(gè)動(dòng)作下 都需要新增條件判斷 , 違反了對(duì)修改關(guān)閉的原則. 程序出錯(cuò)概率大大提升.

如何解決


分析程序擴(kuò)展時(shí)的 可變部分與不變部分

insertCoin returnCoin trunCrank dispense
OnReadyState - - - -
HasCoin - - - -
SoldState - - - -
SoldOutState - - - -
WinnerState *** *** *** ***

不變部分: 從橫向來(lái)看。 用戶能執(zhí)行的操作都是一樣的。 (插入硬幣,按下退幣按鈕,拉下把手)

變化部分: 從橫向看。如果糖果工廠新增的狀態(tài), 對(duì)于用戶每一種動(dòng)作,糖果機(jī)的響應(yīng)都是不同的。都要做出對(duì)應(yīng)的修改

將可變部分抽取出來(lái): 每一種狀態(tài)都會(huì)執(zhí)行4種操作,糖果機(jī)具體的操作與當(dāng)前狀態(tài)有關(guān)。 因此將狀態(tài)與該狀態(tài)下的對(duì)應(yīng)的動(dòng)作行為抽取出來(lái)形成接口。讓每一個(gè)狀態(tài)都實(shí)現(xiàn)該接口。

image

狀態(tài)模式總覽


定義:允許對(duì)象在內(nèi)部狀態(tài)在改變時(shí)改變它的行為,對(duì)象看起來(lái)好像改變了它的類
(將狀態(tài)與該狀態(tài)下的行為封裝成獨(dú)立的類,并將動(dòng)作委托到代表當(dāng)前狀態(tài)的對(duì)象)

  • 模式的理解

    • 類圖

      image
    • 角色

      • 上下文Context
      • 封裝狀態(tài)及該狀態(tài)下行為的 State
      • 具體的狀態(tài)實(shí)習(xí)那類ConcreteState
    • 細(xì)節(jié)

      • 狀態(tài)模式允許一個(gè)對(duì)象基于內(nèi)部狀態(tài)而擁有不同的行為
      • 通過(guò)把每個(gè)狀態(tài)封裝進(jìn)一個(gè)類, 我們把以后需要做的任何改變都局部化了 (改變這個(gè)狀態(tài)下的行為)
      • 狀態(tài)模式與策略模式有相同的類圖 ,但是它們的意圖不同
        • 策略模式通常會(huì)用行為或算法來(lái)配置Context類
        • 狀態(tài)模式允許Context隨著狀態(tài)的改變而改變行為
      • 狀態(tài)轉(zhuǎn)換可以由State類(某個(gè)行為后改變狀態(tài))或者Context(外部設(shè)置狀態(tài)setState)類控制.
      • 使用狀態(tài)模式通常會(huì)導(dǎo)致設(shè)計(jì)中的類的數(shù)據(jù)大量增量(狀態(tài)類)

核心代碼部分

  • 上下文

    public class GumballMachine {
     
        //上下文持有不同的狀態(tài)引用
      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;
          } 
      }
        //上下文提供改變狀態(tài)的接口
        void setState(State state) {
          this.state = state;
      }
     
      public void insertQuarter() {
          state.insertQuarter();  //將行為委托給狀態(tài)對(duì)象來(lái)處理
      }
     
      public void ejectQuarter() {
          state.ejectQuarter();
      }
     
      public void turnCrank() {
          state.turnCrank();
          state.dispense();
      }
     
      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;
          System.out.println("The gumball machine was just refilled; it's new count is: " + this.count);
          state.refill();
      }
    
        getter and setter...
    }
    
  • 狀態(tài)接口

    public interface State {
     
      public void insertQuarter();
      public void ejectQuarter();
      public void turnCrank();
      public void dispense();
      
      public void refill();
    }
    
  • 具體的狀態(tài)
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!");
    }
 
    public void dispense() {
        gumballMachine.releaseBall();
        if (gumballMachine.getCount() == 0) {
            //通過(guò)上下文改變狀態(tài)
            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 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);
      }
    }
    
  • 輸出結(jié)果

    Mighty Gumball, Inc.
    Java-enabled Standing Gumball Model #2004
    Inventory: 10 gumballs
    Machine is waiting for quarter
    
    You inserted a quarter
    You turned...
    A gumball comes rolling out the slot...
    You inserted a quarter
    You turned...
    A gumball comes rolling out the slot...
    
    Mighty Gumball, Inc.
    Java-enabled Standing Gumball Model #2004
    Inventory: 8 gumballs
    Machine is waiting for quarter
    

參考

? 書(shū)籍: HeadFirst設(shè)計(jì)模式

? 代碼參考地址: 我就是那個(gè)地址

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 高階特性 支持垃圾回收對(duì)運(yùn)行時(shí)的一個(gè)深遠(yuǎn)影響是所有代碼都需要做額外的記錄。而類型安全也有一個(gè)重要影響,即要求對(duì)程序...
    懿民閱讀 973評(píng)論 0 0
  • 寒夜[宋]杜耒 寒夜客來(lái)茶當(dāng)酒,竹爐湯沸火初紅。 尋常一樣窗前月,才有梅花便不通。 在小寒的夜晚,杜耒非常的孤獨(dú),...
    BestHenry閱讀 371評(píng)論 0 1