設計模式 ——— 狀態模式

STATE(狀態) ———— 對象行為型模式

意圖

允許一個對象在其內部狀態改變時改變它的行為。對象看起來似乎修改了它的類。

狀態和行為

所謂對象的狀態,通常指的就是對象實例的屬性的值;而行為指的就是對象的功能,再具體點說,行為多半可以對應到方法上。
狀態模式的功能就是分離狀態的行為,通過維護狀態的變化,來調用不同的狀態對應的不同的功能。
也就是說,狀態和行為是相關聯的,它們的關系可以描述為:狀態決定行為。
由于狀態是在運行期被改變的,因此行為也會在運行期,根據狀態的改變而改變,看起來,同一個對象,在不同的運行時刻,行為是不一樣的,就像是類被修改了一樣。

行為的平行性

注意是平行性而不是平等性。所謂平行性指的是各個狀態的行為所處的層次是一樣的,是根據不同的狀態來決定到底走平行線的哪一條,行為是不同的,當然對應的實現也是不同的,相互之間是不可替換的。如圖所示:

而平等性強調的是可替換性,大家是同一行為的不同描述或實現,因此在同一個行為發生的時候,可以根據條件來挑選任意一個實現來進行相應的處理。如圖所示:


大家可能會發現狀態模式的結構和策略模式的結構完全一樣,但是,它們的目的、實現、本質都是完全不一樣的。這個行為之間的特性也是狀態模式和策略模式一個很重要的區別,狀態模式的行為是平行性的,不可相互替換的;而策略模式的行為是平等性的,是可以相互替換的

適用性

在下面的兩種情況下均可使用State模式:
① 一個對象的行為取決于它的狀態,并且它必須在運行時刻根據狀態改變它的行為。
② 一個操作中含有龐大的多分支的條件語句,且這些分支依賴于該對象的狀態。這個狀態通常用一個或多個枚舉常量表示。通常,有多個操作包含這一相同的條件結構。State模式將每一個條件分支放入一個獨立的類中。這使得你可以根據對象自身的情況將對象的狀態作為一個對象,這一對象可以不依賴于其他對象而獨立變化。

結構

狀態模式結構圖.png
  • Context (上下文環境)
    定義客戶感興趣的接口
    維護一個ConcreteState子類的實例,這個實例定義當前狀態。
  • State (狀態)
    定義一個接口以封裝與Context的一個特定狀態相關的行為。
  • ConcreteState subclasses (具體狀態子類)
    每一子類實現一個與Context的一個狀態相關的行為。

協作

  • Context將與狀態相關的請求委托給當前的ConcreteState對象處理。
  • Context可將自身作為一個參數傳遞給處理該請求的狀態對象。這使得狀態對象在必要時可訪問Context。
  • Context是客戶使用的主要接口。客戶可用狀態對象來配置一個Context,一旦一個Context配置完畢,它的客戶不再需要直接與狀態對象打交道。
  • Context或ConcreteState子類都可以決定哪個狀態是另外哪一個的后續者,以及是在何種條件下進行狀態轉換。
狀態維護和轉換控制

所謂狀態的維護,指的就是維護狀態的數據,就是給狀態設置不同的狀態值;而狀態的轉換,指的就是根據狀態的變化來選擇不同的狀態處理對象。在狀態模式中,通常有兩個地方可以進行狀態的維護和轉換控制。
一個就是在上下文(Context)當中,因為狀態本身通常被實現為上下文對象的狀態,因此可以在上下文里面進行狀態維護,當然也就可以控制狀態的轉換了。
另外一個地方就是在狀態的處理類(ConcreteState)里面,當每個狀態處理對象處理完自身狀態所對應的功能后,可以根據需要指定后繼的狀態,以便讓應用能正確處理后續的請求。

狀態模式的優缺點

優點

① 簡化應用邏輯控制
狀態模式使用單獨的類來封裝一個狀態的處理。如果把一個大的程序控制分成很多小塊,每塊定義一個狀態來代表,那么就可以把這些邏輯控制的代碼分散到很多單獨的狀態類當中去,這樣就把著眼點從執行狀態提高到整個對象的狀態,使得代碼結構化和意圖更清晰,從而簡化應用的邏輯控制。
對于依賴于狀態的if-else,理論上來講,也可以改變成應用狀態模式來實現,把每個if或else塊定義一個狀態來代表,那么就可以把塊內的功能代碼移動到狀態處理類去了,從而減少if-else,避免出現巨大的條件語句。

② 更好的分離狀態和行為
狀態模式通過設置所有狀態類的公共接口,把狀態和狀態對應的行為分離開來,把所有與一個特定的狀態相關的行為都放入一個對象中,使得應用程序在控制的時候,只需要關心狀態的切換,而不用關心這個狀態對應的真正處理。

③ 更好的擴展性
引入了狀態處理的公共接口后,使得擴展新的狀態變得非常容易,只需要新增加一個實現狀態處理的公共接口的實現類,然后在進行狀態維護的地方,設置狀態變化到這個新的狀態即可。

④ 顯式化進行狀態轉換
狀態模式為不同的狀態引入獨立的對象,使得狀態的轉換變得更加明確。而且狀態對象可以保證上下文不會發生內部狀態不一致的情況,因為上下文中只有一個變量來記錄狀態對象,只要為這一個變量賦值就可以了。

缺點

① 引入太多的狀態類
狀態模式也有一個很明顯的缺點,一個狀態對應一個狀態處理類,會使得程序引入太多的狀態類,使程序變得雜亂。

示例


Context類:

public class GumballMachine {
   State soldOutState;
   State noQuarterState;
   State hasQuarterState;
   State soldState;
   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);

      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 = count - 1;
      }
   }
   int getCount() {
      return count;
   }
   void refill(int count) {
      this.count = count;
      state = noQuarterState;
   }

    public State getState() {
        return state;
    }

    public State getSoldOutState() {
        return soldOutState;
    }

    public State getNoQuarterState() {
        return noQuarterState;
    }

    public State getHasQuarterState() {
        return hasQuarterState;
    }

    public State getSoldState() {
        return soldState;
    }
   public String toString() {
      StringBuffer result = new StringBuffer();
      result.append("\nMighty Gumball, Inc.");
      result.append("\nJava-enabled Standing Gumball Model #2004");
      result.append("\nInventory: " + count + " gumball");
      if (count != 1) {
         result.append("s");
      }
      result.append("\n");
      result.append("Machine is " + state + "\n");
      return result.toString();
   }
}

State接口:

public interface State {
   public void insertQuarter();
   public void ejectQuarter();
   public void turnCrank();
   public void dispense();
}

ConcreteState 子類:

public class HasQuarterState implements State {
   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...");
      gumballMachine.setState(gumballMachine.getSoldState());
   }

    public void dispense() {
        System.out.println("No gumball dispensed");
    }
   public String toString() {
      return "waiting for turn of crank";
   }
}
public class NoQuarterState implements State {
    GumballMachine gumballMachine;
    public NoQuarterState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
   public void insertQuarter() {
      System.out.println("You inserted a quarter");
      gumballMachine.setState(gumballMachine.getHasQuarterState());
   }
   public void ejectQuarter() {
      System.out.println("You haven't inserted a quarter");
   }
   public void turnCrank() {
      System.out.println("You turned, but there's no quarter");
    }
   public void dispense() {
      System.out.println("You need to pay first");
   }
   public String toString() {
      return "waiting for quarter";
   }
}
public class SoldOutState implements State {
    GumballMachine gumballMachine;
    public SoldOutState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
   public void insertQuarter() {
      System.out.println("You can't insert a quarter, the machine is sold out");
   }
   public void ejectQuarter() {
      System.out.println("You can't eject, you haven't inserted a quarter yet");
   }
   public void turnCrank() {
      System.out.println("You turned, but there are no gumballs");
   }
   public void dispense() {
      System.out.println("No gumball dispensed");
   }
   public String toString() {
      return "sold out";
   }
}
public class SoldState implements State {
    GumballMachine gumballMachine;
    public SoldState(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("Sorry, you already turned the crank");
   }
   public void turnCrank() {
      System.out.println("Turning twice doesn't get you another gumball!");
   }
   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 String toString() {
      return "dispensing a gumball";
   }
}

客戶測試:

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


參考

《Head First 設計模式》
《設計模式:可復用面向對象軟件的基礎》
《研磨設計模式》

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 目錄 本文的結構如下: 引言 什么是狀態模式 模式的結構 典型代碼 代碼示例 狀態模式和策略模式的區別 優點和缺點...
    w1992wishes閱讀 784評論 0 6
  • 定義 狀態模式,又稱為狀態對象模式(Pattern of Object for States),狀態模式是對象的行...
    步積閱讀 1,199評論 0 1
  • 定義 狀態模式,又稱狀態對象模式(Pattern of Objects for States),狀態模式是對象的行...
    Swy2w閱讀 479評論 0 0
  • 今天我們來學習一種行為型模式,狀態模式(State Pattern)。 模式定義 允許一個對象在其內部狀態改變時改...
    HJXANDHMR閱讀 4,523評論 5 12
  • 更好的2017
    笑君劉閱讀 171評論 0 0