設計模式:狀態模式

基本概念

定義

State is a behavioral design pattern that lets an object alter its behavior when its internal state changes. It appears as if the object changed its class.設計模式

當一個對象內在狀態改變時容許其改變行爲,這個對象看起來像改變了其類。 bash

FSM

狀態模式的概念和 FSM (有限狀態機)相似。FSM 表示有限個狀態以及在這些狀態之間的轉移和動做等行爲的數學計算模型。在任何給定時刻,程序能夠處於有限數量的狀態。在任何惟一狀態下,程序的行爲都不一樣,而且能夠將程序從一種狀態切換到另外一種狀態。固然,根據當前狀態,程序可能會或者不會切換到某些其餘狀態。這些稱爲過渡的切換規則也是有限的和預約的。app

舉例說明

iPhone 手機 home 鍵的功能取決於設備的當前狀態:ide

  • 關機狀態: 沒有反應。
  • 開機後首次啓動: 密碼解鎖。
  • 非首次啓動: 密碼解鎖或者指紋解鎖。
  • 啓動後:返回主頁面。

類圖

狀態模式建議爲對象的全部可能狀態建立新的類,並將全部只存在於特定狀態的行爲提取到這些類中。 原始對象稱爲上下文,而不是一個對象單獨實現全部行爲,上下文存儲當前狀態對象的引用,並將全部與狀態相關的工做委託給該對象。測試

它的核心是封裝,狀態的變動引發了行爲的變動,從外部看起來就好像這個對象對應的類發生了改變同樣。狀態模式的類圖如圖所示。 ui

狀態模式中的3個角色。this

  • State——抽象狀態角色spa

    接口或抽象類,負責對象狀態定義,而且封裝環境角色以實現狀態切換。設計

  • ConcreteState——具體狀態角色3d

    每個具體狀態必須完成兩個職責:本狀態的行爲管理以及趨向狀態處理,通俗地說,就是本狀態下要作的事情,以及本狀態如何過渡到其餘狀態。

  • Context——環境角色

    定義客戶端須要的接口,而且負責具體狀態的切換。

這種結構看起來相似於「策略」模式,但有一個關鍵的區別。在狀態模式中,特定狀態可能彼此瞭解,並開始從一個狀態過渡到另外一個狀態,而策略幾乎永遠不會彼此瞭解。

代碼示例 - 電梯

V 1.0

咱們用程序來實現一個簡單的電梯類,電梯有以下動做:開門、關門、運行、中止。先看類圖設計:

電梯接口:

public interface ILift {
     public void open();
     public void close();
     public void run();
     public void stop();
}
複製代碼

電梯的實現類:

public class Lift implements ILift {
     public void close() {
        System.out.println("電梯門關閉...");
     }
     public void open() {
        System.out.println("電梯門開啓...");
     }
     public void run() {
        System.out.println("電梯運行起來...");
     }
     public void stop() {
        System.out.println("電梯中止了...");
     }
}
複製代碼

場景類調用

public class Client {
     public static void main(String[] args) {
        ILift lift = new Lift();
        lift.open();
        lift.close();
        lift.run();
        lift.stop();
     }
}
複製代碼

V 2.0

爲電梯的這4個動做的執行添加前置條件,具體點說就是在特定狀態下才能作特定事。

  • 敞門狀態

    電梯只能作的動做是關門動做。

  • 閉門狀態

    能夠進行的動做是:開門(我不想坐電梯了)、中止(忘記按路層號了)、運行。

  • 運行狀態

    電梯只能作的是中止。

  • 中止狀態

    電梯有兩個可選動做:繼續運行和開門動做。

在接口中定義4個常量,分別表示電梯的4個狀態:敞門狀態、閉門狀態、運行狀態、中止狀態,而後在實現類中電梯的每一次動做發生都要對狀態進行判斷,判斷是否能夠執行。

電梯實現類:

public class Lift implements ILift {
     private int state;
     public void setState(int state) {
        this.state = state;
     }
     //電梯門關閉
     public void close() {
        //電梯在什麼狀態下才能關閉
        switch(this.state) {
            case OPENING_STATE:
                this.closeWithoutLogic();  
                this.setState(CLOSING_STATE);
                break;
            case CLOSING_STATE:  
                break;
            case RUNNING_STATE: 
                break;
            case STOPPING_STATE:  
                break;
        }
     }
     //電梯門開啓
     public void open() {
        //電梯在什麼狀態才能開啓
        switch(this.state){
            ... ...            
        }
     }
     //電梯開始運行起來
     public void run() {
        switch(this.state){
            ... ...    
        }
     }
     //電梯中止
     public void stop() {
        switch(this.state){
            ... ... 
        }
     }
}
複製代碼

一旦咱們開始添加愈來愈多的狀態和與狀態相關的行爲,基於條件判斷的弱點便會暴露出來。大多數方法將包含糟糕的條件判斷,這些條件會根據當前狀態選擇方法的正確行爲。這樣的代碼很難維護,由於對轉換邏輯的任何更改均可能須要在每種方法中更改狀態條件。

隨着項目的發展,這個問題會變得愈來愈大。在設計階段很難預測全部可能的狀態和轉換。所以,隨着時間的推移,這個類可能會變得臃腫。

V 3.0

如何用狀態模式來解決這個問題?

抽象電梯代碼:

public abstract class LiftState{
     protected Context context;
     public void setContext(Context _context){
        this.context = _context;
     }
     public abstract void open();
     public abstract void close();
     public abstract void run();
     public abstract void stop();
}
複製代碼

敞門狀態類:

public class OpenningState extends LiftState {
     //開啓固然能夠關閉了,我就想測試一下電梯門開關功能
     @Override
     public void close() {
        //狀態修改
        super.context.setLiftState(Context.closeingState);
        //動做委託爲CloseState來執行
        super.context.getLiftState().close();
     }
     //打開電梯門
     @Override
     public void open() {
        System.out.println("電梯門開啓...");
     }
     //門開着時電梯就運行跑,這電梯,嚇死你!
     @Override
     public void run() {
        //do nothing;
     }
     //開門還不中止?
     public void stop() {
        //do nothing;
     }
}
複製代碼

關門狀態類:

public class ClosingState extends LiftState {
     //電梯門關閉,這是關閉狀態要實現的動做
     @Override
     public void close() {
        System.out.println("電梯門關閉...");
     }
     //電梯門關了再打開
     @Override
     public void open() {
        super.context.setLiftState(Context.openningState);  //置爲敞門狀態
        super.context.getLiftState().open();
     }
     //電梯門關了就運行,這是再正常不過了
     @Override
     public void run() {
        super.context.setLiftState(Context.runningState); //設置爲運行狀態
        super.context.getLiftState().run();
     }
     //電梯門關着,我就不按樓層
     @Override
     public void stop() {
        super.context.setLiftState(Context.stoppingState);  //設置爲中止狀態
        super.context.getLiftState().stop();
     }
}
複製代碼

上下文類

public class Context {
     //定義出全部的電梯狀態
     public final static OpenningState openningState = new OpenningState();
     public final static ClosingState closeingState = new ClosingState();
     public final static RunningState runningState = new RunningState();
     public final static StoppingState stoppingState = new StoppingState();
     //定義一個當前電梯狀態
     private LiftState liftState;
     public LiftState getLiftState() {
        return liftState;
     }
     public void setLiftState(LiftState liftState) {
        this.liftState = liftState;
        //把當前的環境通知到各個實現類中
        this.liftState.setContext(this);
     }
     public void open(){
        this.liftState.open();
     }
     public void close(){
        this.liftState.close();
     }
     public void run(){
        this.liftState.run();
     }
     public void stop(){
        this.liftState.stop();
     }
}
複製代碼

調用方:

public class Client {
     public static void main(String[] args) {
             Context context = new Context();
             context.setLiftState(new ClosingState());
             context.open();
             context.close();
             context.run();
             context.stop();
     }
}
複製代碼

總結

狀態模式的優勢

  1. 符合單一職責原則:將與特定狀態相關的代碼組織到單獨的類中。

  2. 符合開放封閉原則:在不更改現有狀態類或上下文的狀況下引入新狀態。

  3. 避免了過長的if 或者是switch判斷。

狀態模式的缺點

  1. 若是有不少種狀態,子類會太多,類膨脹。一個事物有不少個狀態也不稀奇,若是徹底使用狀態模式就會有太多的子類,很差管理。
  2. 若是類只有幾個狀態或不多更改,應用狀態模式可能會過分設計。

參考:

  1. 設計模式之禪
  2. State
相關文章
相關標籤/搜索