在闡述狀態模式之前,先來看一個例子。一個銀行取款問題: 如果賬戶余額大於0,則正常取款;如果余額在-2000和0之間,則透支取款;如果余額小於-2000,則賬戶處於凍結狀態,無法進行取款操作。
實現代碼如下:
//銀行賬戶 class Account { private: //余額 int m_nBalance; public: //取款操作 void WithDraw() { if( m_nBalance > 0 ) { cout << "正常取款狀態" << endl; } else if( m_nBalance > -2000 ) { cout << "透支狀態" << endl; } else { cout << "凍結狀態" << endl; } } };執行取款這一操作,存在正常狀態、透支狀態、凍結狀態三種狀態。不同狀態下,取款操作對應有不同的行為。如果需要添加一種新的狀態,如:賬戶余額小於-10000,直接注銷此賬戶,並且接受法院的傳票,得修改上述代碼。執行某一個操作,需要判斷這一操作是在哪一個狀態下執行的,判斷該狀態下是否具有該方法,以及特定狀態下如何實現該方法,將導致大量的if...else條件判斷,新增新的狀態,得修改源代碼,違背"開放封閉原則"。
因此有必要對這些狀態進行封裝,將狀態的行為封裝到具體的狀態中。為了解決這些問題,我們可以使用狀態模式,在狀態模式中,我們將對象在每一個狀態下的行為和狀態轉移語句封裝在一個個狀態類中,通過這些狀態類來分散冗長的條件轉移語句,讓系統具有更好的靈活性和可擴展性。
1、狀態模式概述
狀態模式用於解決系統中復雜對象的狀態轉換以及不同狀態下行為的封裝問題。當系統中某個對象存在多個狀態,這些狀態之間可以進行轉換,而且對象在不同狀態下行為不相同時可以使用狀態模式。狀態模式將一個對象的狀態從該對象中分離出來,封裝到專門的狀態類中,使得對象狀態可以靈活變化,對於客戶端而言,無須關心對象狀態的轉換以及對象所處的當前狀態,無論對於何種狀態的對象,客戶端都可以一致處理。
狀態模式(State Pattern):允許一個對象在其內部狀態改變時改變它的行為,對象看起來似乎修改了它的類。其別名為狀態對象(Objects for States),狀態模式是一種對象行為型模式。
在狀態模式中引入了抽象狀態類和具體狀態類,它們是狀態模式的核心。
狀態模式結構圖
在狀態模式結構圖中包含如下幾個角色:
Context(環境類):環境類又稱為上下文類,它是擁有多種狀態的對象。由於環境類的狀態存在多樣性且在不同狀態下對象的行為有所不同,因此將狀態獨立出去形成單獨的狀態類。在環境類中維護一個抽象狀態類State的實例,這個實例定義當前狀態,在具體實現時,它是一個State子類的對象。
State(抽象狀態類):它用於定義一個接口以封裝與環境類的一個特定狀態相關的行為,在抽象狀態類中聲明了各種不同狀態對應的方法,而在其子類中實現類這些方法,由於不同狀態下對象的行為可能不同,因此在不同子類中方法的實現可能存在不同,相同的方法可以寫在抽象狀態類中。
ConcreteState(具體狀態類):它是抽象狀態類的子類,每一個子類實現一個與環境類的一個狀態相關的行為,每一個具體狀態類對應環境的一個具體狀態,不同的具體狀態類其行為有所不同。
在狀態模式中,我們將對象在不同狀態下的行為封裝到不同的狀態類中,為了讓系統具有更好的靈活性和可擴展性,同時對各狀態下的共有行為進行封裝,我們需要對狀態進行抽象,引入了抽象狀態類角色,其典型代碼如下所示:
class State { public: //聲明抽象業務方法,不同的具體狀態類可以不同的實現 void handle(); };
在抽象狀態類的子類即具體狀態類中實現了在抽象狀態類中聲明的業務方法,不同的具體狀態類可以提供完全不同的方法實現,在實際使用時,在一個狀態類中可能包含多個業務方法,如果在具體狀態類中某些業務方法的實現完全相同,可以將這些方法移至抽象狀態類,實現代碼的復用,典型的具體狀態類代碼如下所示
class ConcreteState : public State { public: void handle() { //方法具體實現代碼 } }環境類維持一個對抽象狀態類的引用,通過setState()方法可以向環境類注入不同的狀態對象,再在環境類的業務方法中調用狀態對象的方法,典型代碼如下所示。
class Context { private: //維持一個對抽象狀態對象的引用 State state; //其他屬性值,該屬性值的變化可能會導致對象狀態發生變化 int value; public: //設置狀態對象 void setState(State state) { this.state = state; } //調用狀態對象的業務方法 void request() { state.handle(); //其他代碼 } }環境類實際上是真正擁有狀態的對象,我們只是將環境類中與狀態有關的代碼提取出來封裝到專門的狀態類中,環境類Context與抽象狀態類State之間是一種關聯關系。和策略模式相似,策略模式封裝的是一個個具體策略,環境類Context引用一個具體的策略;而狀態模式封裝的是一個個具體的狀態,環境類Context也維持了一個對具體狀態的引用。兩者都是通過組合的方式,實現軟件復用的功能。