(Boolan)C++設計模式 <十> ——狀態模式(State)和備忘錄(Memento)

“狀態變化”模式

在組建構建過程中,某些對象的狀態經常面臨變化,如何對這些變化進行有效的管理?同時又維持高層模塊的穩定?“狀態變化”模式為這一個問題提供了一種解決方案。

  • 典型模式
    • State
    • Memento

狀態模式(State)

允許一個對象在其內部狀態改變是改變它的行為。從而使對像看起來似乎修改其行為。
——《設計模式》GoF

  • 動機
    在軟件構建過程中,某些對象的狀態如果改變,其行為也會隨之而發生變化,比如文檔處于只讀狀態,其支持的行為和讀寫狀態支持的行為就可能會完全不同。

假設有一個網絡的應用,移動有三種狀態,打開、關閉和連接。
操作針對不同的狀態會執行不同的行為。(狀態不同,行為不同)
同時執行一次操作完畢后,也會更改狀態。

enum NetworkState
{
    Network_Open,
    Network_Close,
    Network_Connect,
};

class NetworkProcessor{
    
    NetworkState state;

public:
    
    void Operation1(){
        if (state == Network_Open){

            //**********
            state = Network_Close;
        }
        else if (state == Network_Close){

            //..........
            state = Network_Connect;
        }
        else if (state == Network_Connect){

            //$$$$$$$$$$
            state = Network_Open;
        }
    }

    public void Operation2(){

        if (state == Network_Open){
            
            //**********
            state = Network_Connect;
        }
        else if (state == Network_Close){

            //.....
            state = Network_Open;
        }
        else if (state == Network_Connect){

            //$$$$$$$$$$
            state = Network_Close;
        }
    
    }

    public void Operation3(){

    }
};

對于上面的代碼來說,未來狀態是否會發生變化?如果出現了新的變化,那么之后的所有的代碼,全部都需要改變,違背了開閉原則。
那么根據Strategy模式的啟發,我們把狀態的向上抽象,每個自己實現出來的狀態,將所有跟狀態有關的操作全都變成具體的狀態對像的行為。每個狀態也可以次啊用單例的模式。每個狀態的操作之后,可以根據他的操作完后,在操作的方法中,將自動定義下一個狀態。對于調用者來說,每次的狀態改變只需要將指針指向操作所改變的下一個對象即可,那么使用者完全不需要考慮下一個狀態是什么,因為在制定狀態的操作中已經改變好了。

class NetworkState{

public:
    NetworkState* pNext;
    virtual void Operation1()=0;
    virtual void Operation2()=0;
    virtual void Operation3()=0;

    virtual ~NetworkState(){}
};


class OpenState :public NetworkState{
    
    static NetworkState* m_instance;
public:
    static NetworkState* getInstance(){
        if (m_instance == nullptr) {
            m_instance = new OpenState();
        }
        return m_instance;
    }

    void Operation1(){
        
        //**********
        pNext = CloseState::getInstance();
    }
    
    void Operation2(){
        
        //..........
        pNext = ConnectState::getInstance();
    }
    
    void Operation3(){
        
        //$$$$$$$$$$
        pNext = OpenState::getInstance();
    }
    
    
};

class CloseState:public NetworkState{ }
//...


class NetworkProcessor{
    
    NetworkState* pState;
    
public:
    
    NetworkProcessor(NetworkState* pState){
        
        this->pState = pState;
    }
    
    void Operation1(){
        //...
        pState->Operation1();
        pState = pState->pNext;
        //...
    }
    
    void Operation2(){
        //...
        pState->Operation2();
        pState = pState->pNext;
        //...
    }
    
    void Operation3(){
        //...
        pState->Operation3();
        pState = pState->pNext;
        //...
    }

};
State的UML

要點總結

  • State模式將所有與一個特定狀態相關的行為都放入一個State的子類對象中,在對像狀態切換時, 切換相應的對象;但同時維持State的接口,這樣實現了具體操作與狀態轉換之間的解耦。
  • 為不同的狀態引入不同的對象使得狀態轉換變得更加明確,而且可以保證不會出現狀態不一致的情況,因為轉換是原子性的——即要么徹底轉換過來,要么不轉換。
  • 如果State對象沒有實例變量,那么各個上下文可以共享同一個State對象,從而節省對象開銷。

備忘錄(Memento)

在不破壞封裝性的前提下,不活一個對象的內部狀態,并在該對像之外保存這個狀態。這樣以后就可以將該對像恢復到原想保存的狀態。
——《設計模式》GoF

  • 動機
    在軟件構建過程中,某些對象的狀態在轉會過程中,可能由于某種需求,要求程序能夠回溯到對像之前處于某個點時的狀態。如果使用一些公有借口來讓其它對象得到對象的狀態,便會暴露對象的實現細節。

class Memento
{
    string state;
    //..
public:
    Memento(const string & s) : state(s) {}
    string getState() const { return state; }
    void setState(const string & s) { state = s; }
};



class Originator
{
    string state;
    //....
public:
    Originator() {}
    Memento createMomento() {
        Memento m(state);
        return m;
    }
    void setMomento(const Memento & m) {
        state = m.getState();
    }
};



int main()
{
    Originator orginator;
    
    //捕獲對象狀態,存儲到備忘錄
    Memento mem = orginator.createMomento();
    
    //... 改變orginator狀態
    
    //從備忘錄中恢復
    orginator.setMomento(memento);

   
}

假設Originator是一個需要被保存的對象,他的內部可能有很多的狀態,我們有一個需求:在某一個時間節點,為這個對象拍一個快照。那么我們設計了一個Memento對象,其中具有和Originator需要備份的屬性。Memento具有供外界調用的接口。那么被創建的備份,應該是穩定的,外界只有讀取信息的結構,沒有修改信息的接口。
在Orginator還應該具有從Memento恢復的方法,來依靠之前所存儲的快照進行恢復。

以上僅僅是一個示意性的偽碼描述,也可以通過其他的手段來實現Memento的過程,比如對象的序列化和反序列化等等。

Memento的UML

要點總結
備忘錄(Memento)存儲原發器(Originator)對象的內部狀態,在需要時恢復原發器的狀態。
Memento模式的核心是信息隱藏,即Originator需要向外接隱藏信息,保持其封裝性。但同時又需要將其狀態保持到外界(Memento)
由于現代語言運行時(如C#、java等)都具有相當的對象序列化支持,因此往往采用效率較高、有較容易正確實現的序列化方案來實現Memento模式。
由于《設計模式》是在94年定義的,現在很多的技術發展已經變化,現在Memento的實現方法已經過時了,現在在使用其他的方式在做這件事,但是對于思想并沒有發生變化。在備份的過程中,還需要保持封裝的時候,將信息保存到外部。

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

推薦閱讀更多精彩內容