設計模式學習專欄十一--------狀態(tài)模式

設計模式學習專欄十一--------狀態(tài)模式

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

價值觀念: 通過改變對象內部的狀態(tài)來幫助對象控制自己的行為

場景


設計一個萬能糖果機 , 我們希望設計盡可能有彈性 , 而且將來我們可能要為它增加更多的行為~

image

剛開始的設計方式

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

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

新功能引入 , 我們加入了一個新的游戲狀態(tài) , 當曲柄被轉動時,有10%的幾率掉下來的是兩顆糖果(贏家狀態(tài))

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

如何解決


分析程序擴展時的 可變部分與不變部分

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

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

變化部分: 從橫向看膏潮。如果糖果工廠新增的狀態(tài)裸弦, 對于用戶每一種動作劲适,糖果機的響應都是不同的楷掉。都要做出對應的修改

將可變部分抽取出來: 每一種狀態(tài)都會執(zhí)行4種操作,糖果機具體的操作與當前狀態(tài)有關霞势。 因此將狀態(tài)與該狀態(tài)下的對應的動作行為抽取出來形成接口烹植。讓每一個狀態(tài)都實現(xiàn)該接口。

image

狀態(tài)模式總覽


定義:允許對象在內部狀態(tài)在改變時改變它的行為,對象看起來好像改變了它的類
(將狀態(tài)與該狀態(tài)下的行為封裝成獨立的類,并將動作委托到代表當前狀態(tài)的對象)

  • 模式的理解

    • 類圖

      image
    • 角色

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

      • 狀態(tài)模式允許一個對象基于內部狀態(tài)而擁有不同的行為
      • 通過把每個狀態(tài)封裝進一個類, 我們把以后需要做的任何改變都局部化了 (改變這個狀態(tài)下的行為)
      • 狀態(tài)模式與策略模式有相同的類圖 ,但是它們的意圖不同
        • 策略模式通常會用行為或算法來配置Context類
        • 狀態(tài)模式允許Context隨著狀態(tài)的改變而改變行為
      • 狀態(tài)轉換可以由State類(某個行為后改變狀態(tài))或者Context(外部設置狀態(tài)setState)類控制.
      • 使用狀態(tài)模式通常會導致設計中的類的數(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)對象來處理
      }
     
      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) {
            //通過上下文改變狀態(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);
      }
    }
    
  • 輸出結果

    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
    

參考

? 書籍: HeadFirst設計模式

? 代碼參考地址: 我就是那個地址

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末愕贡,一起剝皮案震驚了整個濱河市草雕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌固以,老刑警劉巖墩虹,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡败晴,警方通過查閱死者的電腦和手機浓冒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來尖坤,“玉大人稳懒,你說我怎么就攤上這事÷叮” “怎么了场梆?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長纯路。 經常有香客問我或油,道長,這世上最難降的妖魔是什么驰唬? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任顶岸,我火速辦了婚禮,結果婚禮上叫编,老公的妹妹穿的比我還像新娘辖佣。我一直安慰自己,他們只是感情好搓逾,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布卷谈。 她就那樣靜靜地躺著,像睡著了一般霞篡。 火紅的嫁衣襯著肌膚如雪世蔗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天朗兵,我揣著相機與錄音污淋,去河邊找鬼。 笑死矛市,一個胖子當著我的面吹牛芙沥,可吹牛的內容都是我干的。 我是一名探鬼主播浊吏,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼救氯!你這毒婦竟也來了找田?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤着憨,失蹤者是張志新(化名)和其女友劉穎墩衙,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡漆改,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年心铃,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挫剑。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡去扣,死狀恐怖,靈堂內的尸體忽然破棺而出樊破,到底是詐尸還是另有隱情愉棱,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布哲戚,位于F島的核電站奔滑,受9級特大地震影響,放射性物質發(fā)生泄漏顺少。R本人自食惡果不足惜朋其,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望脆炎。 院中可真熱鬧梅猿,春花似錦、人聲如沸腕窥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽簇爆。三九已至癞松,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間入蛆,已是汗流浹背响蓉。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留哨毁,地道東北人枫甲。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像扼褪,于是被迫代替她去往敵國和親想幻。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內容

  • 高階特性 支持垃圾回收對運行時的一個深遠影響是所有代碼都需要做額外的記錄话浇。而類型安全也有一個重要影響脏毯,即要求對程序...
    懿民閱讀 944評論 0 0
  • 寒夜[宋]杜耒 寒夜客來茶當酒,竹爐湯沸火初紅幔崖。 尋常一樣窗前月食店,才有梅花便不通渣淤。 在小寒的夜晚,杜耒非常的孤獨吉嫩,...
    BestHenry閱讀 355評論 0 1