1. 程式人生 > >C++狀態模式詳解--設計模式(15)

C++狀態模式詳解--設計模式(15)

State模式來源: 

 每個人、事物在不同的狀態下會有不同表現(動作),而一個狀態又會在不同的表現下轉移到下一個不同的狀態(State)。最簡單的一個生活中的例子就是:地鐵入口處,如果你放入正確的地鐵票,門就會開啟讓你通過。在出口處也是驗票,如果正確你就可以ok,否則就不讓你通過(如果你動作野蠻,或許會有報警(Alarm))。
      有限狀態自動機(FSM)也是一個典型的狀態不同,對輸入有不同的響應(狀態轉移)。通常我們在實現這類系統會使用到很多的Switch/Case語句,Case某種狀態,發生什麼動作,Case另外一種狀態,則發生另外一種狀態。但是這種實現方式至少有以下兩個問題:
      (1).當狀態數目不是很多的時候,Switch/Case可能可以搞定。但是當狀態數目很多的時候(實際系統中也正是如此),維護一大組的Switch/Case語句將是一件異常困難並且容易出錯的事情。

      (2).狀態邏輯和動作實現沒有分離。在很多的系統實現中,動作的實現程式碼直接寫在狀態的邏輯當中。這帶來的後果就是系統的擴充套件性和維護得不到保證。

State模式作用:

 State模式就是被用來解決上面列出的兩個問題的,在State模式中我們將狀態邏輯和動作實現進行分離。當一個操作中要維護大量的case分支語句,並且這些分支依賴於物件的狀態。State模式將每一個分支都封裝到獨立的類中。

State模式UML結構圖如圖1所示:

                     

State模式的構成:

State類:抽象狀態類,定義一個介面以封裝與Context的一個特定狀態相關的行為。

ConcreteState類:具體狀態,每一個子類實現一個與Context的一個狀態相關的行為。

Context類:維護一個ConcreteState子類的例項,這個例項定義當前的狀態。

State模式的程式碼示例:

State.h

#ifndef _STATE_H_
#define _STATE_H_

class Context;
class State
{
public:
    virtual void Handle(Context* pContext)=0;
    ~State();
protected:
    State();
private:
};

class ConcreteStateA : public State
{
public:
    ConcreteStateA();
    ~ConcreteStateA();
    virtual void Handle(Context* pContext);
protected:
private:
};

class ConcreteStateB : public State
{
public:
    ConcreteStateB();
    ~ConcreteStateB();
    virtual void Handle(Context* pContext);
protected:
private:
};

class ConcreteStateC : public State
{
public:
    ConcreteStateC();
    ~ConcreteStateC();
    virtual void Handle(Context* pContext);
protected:
private:
};

class Context
{
public:
    Context(State* pState);
    ~Context();
    void Request();
    void ChangeState(State* pState);
protected:
private:
    State* _state;
};

#endif

State.cpp

#include "State.h"
#include <iostream>
using namespace std;
State::State()
{}
State::~State()
{}
ConcreteStateA::ConcreteStateA()
{}
ConcreteStateA::~ConcreteStateA()
{}
//執行該狀態的行為並改變狀態
void ConcreteStateA::Handle(Context* pContext)
{
    cout << "ConcreteStateA" << endl;
    pContext->ChangeState(new ConcreteStateB());
}
ConcreteStateB::ConcreteStateB()
{}
ConcreteStateB::~ConcreteStateB()
{}
//執行該狀態的行為並改變狀態
void ConcreteStateB::Handle(Context* pContext)
{
    cout << "ConcreteStateB" << endl;
    pContext->ChangeState(new ConcreteStateC());
}
ConcreteStateC::ConcreteStateC()
{}
ConcreteStateC::~ConcreteStateC()
{}
//執行該狀態的行為並改變狀態
void ConcreteStateC::Handle(Context* pContext)
{
    cout << "ConcreteStateC" << endl;
    pContext->ChangeState(new ConcreteStateA());
}

//定義_state的初始狀態
Context::Context(State* pState)
{
    this->_state = pState;
}
Context::~Context()
{}
//對請求做處理,並設定下一狀態
void Context::Request()
{
    if(NULL != this->_state)
    {
        this->_state->Handle(this);
    }
}
//改變狀態
void Context::ChangeState(State* pState)
{
    this->_state = pState;
}
Main.cpp
#include "State.h"

int main()
{
    State* pState = new ConcreteStateA();
    Context* pContext = new Context(pState);
    pContext->Request();
    pContext->Request();
    pContext->Request();
    pContext->Request();
    pContext->Request();
    return 0;
}

State模式使用場景:

State模式的應用也非常廣泛,從最高層邏輯使用者介面GUI到最底層的通訊協議(例如利用State模式模擬實現一個TCP連線的類。)都有其用武之地。

        State模式和Strategy模式又很大程度上的相似:它們都有一個Context類,都是通過委託(組合)給一個具有多個派生類的多型基類實現Context的演算法邏輯。兩者最大的差別就是State模式中派生類持有指向Context物件的引用,並通過這個引用呼叫Context中的方法,但在Strategy模式中就沒有這種情況。因此可以說一個State例項同樣是Strategy模式的一個例項,反之卻不成立。實際上State模式和Strategy模式的區別還在於它們所關注的點不盡相同:State模式主要是要適應物件對於狀態改變時的不同處理策略的實現,而Strategy則主要是具體演算法和實現介面的解耦(coupling),Strategy模式中並沒有狀態的概念(雖然很多時候有可以被看作是狀態的概念),並且更加不關心狀態的改變了。
       State模式很好地實現了物件的狀態邏輯和動作實現的分離,狀態邏輯分佈在State的派生類中實現,而動作實現則可以放在Context類中實現(這也是為什麼State派生類需要擁有一個指向Context的指標)。這使得兩者的變化相互獨立,改變State的狀態邏輯可以很容易複用Context的動作,也可以在不影響State派生類的前提下建立Context的子類來更改或替換動作實現。

State模式經典示例:

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

1->main(),客戶

2->CLiftState,電梯狀態抽象類

3->CCloseingState,電梯門關閉

4->COpenningState,電梯門開啟

5->CRunningState,電梯執行

6->CStoppingState,電梯停止

7->CContext,電梯的控制面板

       說明:CContext保持電梯的狀態,並提供操作的介面函式。當函式被呼叫時,CContext直接呼叫當前狀態的相應函式。由狀態的介面函式來確定是否可以執行這個動作,以及修改狀態為執行這個動作後的狀態。

       看程式碼:第一塊是不使用模式的做法,第二塊是使用模式的做法,在main()函式裡會有呼叫的方式。

<span style="font-size:14px;"><span style="font-size:14px;">//ILift.h
#pragma once
class ILift
{
public:

    ILift(void)
    {
    }

    virtual ~ILift(void)
    {
    }
    static const int OPENING_STATE = 1;
    static const int CLOSING_STATE = 2;
    static const int RUNNING_STATE = 3;
    static const int STOPPING_STATE = 4;
    virtual void SetState(int state) = 0;
    virtual void Open() = 0;
    virtual void Close() = 0;
    virtual void Run() = 0;
    virtual void Stop() = 0;
};

//Lift.h
#pragma once
#include "ilift.h"
class CLift :
    public ILift
{
public:
    CLift(void);
    ~CLift(void);
    void SetState(int state);
    void Open();
    void Close();
    void Run();
    void Stop();
private:
    int m_state;
    void OpenWithoutLogic();
    void CloseWithoutLogic();
    void RunWithoutLogic();
    void StopWithoutLogic();
};

//Lift.cpp
#include "StdAfx.h"
#include "Lift.h"
#include <iostream>
using std::cout;
using std::endl;

CLift::CLift(void)
{
    this->m_state = 0;
}

CLift::~CLift(void)
{
}

void CLift::SetState(int state)
{
    this->m_state = state;
}

void CLift::Open()
{
    switch(this->m_state)
    {
    case OPENING_STATE:
        break;
    case CLOSING_STATE:
        this->OpenWithoutLogic();
        this->SetState(OPENING_STATE);
        break;
    case RUNNING_STATE:
        break;
    case STOPPING_STATE:
        this->OpenWithoutLogic();
        this->SetState(OPENING_STATE);
        break;
    }
}

void CLift::Close()
{
    switch(this->m_state)
    {
    case OPENING_STATE:
        this->CloseWithoutLogic();
        this->SetState(CLOSING_STATE);
        break;
    case CLOSING_STATE:
        break;
    case RUNNING_STATE:
        break;
    case STOPPING_STATE:
        break;
    }
}

void CLift::Run()
{
    switch(this->m_state)
    {
    case OPENING_STATE:
        break;
    case CLOSING_STATE:
        this->RunWithoutLogic();
        this->SetState(RUNNING_STATE);
        break;
    case RUNNING_STATE:
        break;
    case STOPPING_STATE:
        this->RunWithoutLogic();
        this->SetState(RUNNING_STATE);
        break;
    }
}

void CLift::Stop()
{
    switch(this->m_state)
    {
    case OPENING_STATE:
        break;
    case CLOSING_STATE:
        this->StopWithoutLogic();
        this->SetState(CLOSING_STATE);
        break;
    case RUNNING_STATE:
        this->StopWithoutLogic();
        this->SetState(CLOSING_STATE);
        break;
    case STOPPING_STATE:
        break;
    }
}

void CLift::OpenWithoutLogic()
{
    cout << "電梯門開啟..." << endl;
}

void CLift::CloseWithoutLogic()
{
    cout << "電梯門關閉..." << endl;
}

void CLift::RunWithoutLogic()
{
    cout << "電梯上下跑起來..." << endl;
}

void CLift::StopWithoutLogic()
{
    cout << "電梯停止了..." << endl;
}</span></span>
<span style="font-size:14px;"><span style="font-size:14px;">//LiftState.h
#pragma once
class CContext;
class CLiftState
{
public:
    CLiftState(void);
    virtual ~CLiftState(void);
    void SetContext(CContext *pContext);
    virtual void Open() = 0;
    virtual void Close() = 0;
    virtual void Run() = 0;
    virtual void Stop() = 0;
protected:
    CContext *m_pContext;
};

//LiftState.cpp
#include "StdAfx.h"
#include "LiftState.h"

CLiftState::CLiftState(void)
{
}

CLiftState::~CLiftState(void)
{
}

void CLiftState::SetContext( CContext *pContext )
{
    m_pContext = pContext;
}

//CloseingState.h
#pragma once
#include "liftstate.h"
class CCloseingState :
    public CLiftState
{
public:
    CCloseingState(void);
    ~CCloseingState(void);
    void Open();
    void Close();
    void Run();
    void Stop();
};

//CloseingState.cpp
#include "StdAfx.h"
#include "CloseingState.h"
#include "Context.h"
#include <iostream>
using std::cout;
using std::endl;

CCloseingState::CCloseingState(void)
{
}

CCloseingState::~CCloseingState(void)
{
}

void CCloseingState::Open()
{
    this->CLiftState::m_pContext->SetLiftState(CContext::pOpenningState);
    this->CLiftState::m_pContext->GetLiftState()->Open();
}

void CCloseingState::Close()
{
    cout << "電梯門關閉..." << endl;
}

void CCloseingState::Run()
{
    this->CLiftState::m_pContext->SetLiftState(CContext::pRunningState);
    this->CLiftState::m_pContext->GetLiftState()->Run();
}

void CCloseingState::Stop()
{
    this->CLiftState::m_pContext->SetLiftState(CContext::pStoppingState);
    this->CLiftState::m_pContext->GetLiftState()->Stop();
}

//OpenningState.h
#pragma once
#include "liftstate.h"
class COpenningState :
    public CLiftState
{
public:
    COpenningState(void);
    ~COpenningState(void);
    void Open();
    void Close();
    void Run();
    void Stop();
};

//OpenningState.cpp
#include "StdAfx.h"
#include "OpenningState.h"
#include "Context.h"
#include <iostream>
using std::cout;
using std::endl;

COpenningState::COpenningState(void)
{
}

COpenningState::~COpenningState(void)
{
}

void COpenningState::Open()
{
    cout << "電梯門開啟..." << endl;
}

void COpenningState::Close()
{
    this->CLiftState::m_pContext->SetLiftState(CContext::pCloseingState);
    this->CLiftState::m_pContext->GetLiftState()->Close();
}

void COpenningState::Run()
{
    //do nothing
}

void COpenningState::Stop()
{
    //do nothing
}

//RunningState.h
#pragma once
#include "liftstate.h"
class CRunningState :
    public CLiftState
{
public:
    CRunningState(void);
    ~CRunningState(void);
    void Open();
    void Close();
    void Run();
    void Stop();
};

//RunningState.cpp
#include "StdAfx.h"
#include "RunningState.h"
#include "Context.h"
#include <iostream>
using std::cout;
using std::endl;

CRunningState::CRunningState(void)
{
}

CRunningState::~CRunningState(void)
{
}

void CRunningState::Open()
{
    //do nothing
}

void CRunningState::Close()
{
    //do nothing
}

void CRunningState::Run()
{
    cout << "電梯上下跑..." << endl;
}

void CRunningState::Stop()
{
    this->CLiftState::m_pContext->SetLiftState(CContext::pStoppingState);
    this->CLiftState::m_pContext->GetLiftState()->Stop();
}

//StoppingState.h
#pragma once
#include "liftstate.h"
class CStoppingState :
    public CLiftState
{
public:
    CStoppingState(void);
    ~CStoppingState(void);
    void Open();
    void Close();
    void Run();
    void Stop();
};

//StoppingState.cpp
#include "StdAfx.h"
#include "StoppingState.h"
#include "Context.h"
#include <iostream>
using std::cout;
using std::endl;

CStoppingState::CStoppingState(void)
{
}

CStoppingState::~CStoppingState(void)
{
}

void CStoppingState::Open()
{
    this->CLiftState::m_pContext->SetLiftState(CContext::pOpenningState);
    this->CLiftState::m_pContext->GetLiftState()->Open();
}

void CStoppingState::Close()
{
    //do nothing
}

void CStoppingState::Run()
{
    this->CLiftState::m_pContext->SetLiftState(CContext::pRunningState);
    this->CLiftState::m_pContext->GetLiftState()->Run();
}

void CStoppingState::Stop()
{
    cout << "電梯停止了..." << endl;
}</span></span>
<span style="font-size:14px;"><span style="font-size:14px;">//Contex.h
#pragma once
#include "LiftState.h"
#include "OpenningState.h"
#include "CloseingState.h"
#include "RunningState.h"
#include "StoppingState.h"
class CContext
{
public:
    CContext(void);
    ~CContext(void);
    static COpenningState *pOpenningState;
    static CCloseingState *pCloseingState;
    static CRunningState *pRunningState;
    static CStoppingState *pStoppingState;
    CLiftState * GetLiftState();
    void SetLiftState(CLiftState *pLiftState);
    void Open();
    void Close();
    void Run();
    void Stop();
private:
    CLiftState *m_pLiftState;
};

//Context.cpp
#include "StdAfx.h"
#include "Context.h"
COpenningState* CContext::pOpenningState = NULL;
CCloseingState* CContext::pCloseingState = NULL;
CRunningState* CContext::pRunningState = NULL;
CStoppingState* CContext::pStoppingState = NULL;

CContext::CContext(void)
{
    m_pLiftState = NULL;
    pOpenningState = new COpenningState();
    pCloseingState = new CCloseingState();
    pRunningState = new CRunningState();
    pStoppingState = new CStoppingState();
}

CContext::~CContext(void)
{
    delete pOpenningState;
    pOpenningState = NULL;
    delete pCloseingState;
    pCloseingState = NULL;
    delete pRunningState;
    pRunningState = NULL;
    delete pStoppingState;
    pStoppingState = NULL;
}

CLiftState * CContext::GetLiftState()
{
    return m_pLiftState;
}

void CContext::SetLiftState(CLiftState *pLiftState)
{
    this->m_pLiftState = pLiftState;
    this->m_pLiftState->SetContext(this);
}

void CContext::Open()
{
    this->m_pLiftState->Open();
}

void CContext::Close()
{
    this->m_pLiftState->Close();
}

void CContext::Run()
{
    this->m_pLiftState->Run();
}

void CContext::Stop()
{
    this->m_pLiftState->Stop();
}

// State.cpp : 定義控制檯應用程式的入口點。
#include "stdafx.h"
#include "ILift.h"
#include "Lift.h"
#include "Context.h"
#include "OpenningState.h"
#include "CloseingState.h"
#include "RunningState.h"
#include "StoppingState.h"
#include <iostream>
using std::cout;
using std::endl;

void DoIt()
{
    //ILift.h, Lift.h, Lift.cpp
    ILift *pLift = new CLift();
    pLift->SetState(ILift::STOPPING_STATE);//電梯的初始條件是停止狀態。
    pLift->Open();//首先是電梯門開啟,人進去
    pLift->Close();//然後電梯門關閉
    pLift->Run();//再然後,電梯跑起來,向上或者向下
    pLift->Stop();//最後到達目的地,電梯停下來
    delete pLift;
}


void DoNew()
{
    //LiftState.h, LiftState.cpp, OpenningState.h, CloseingState.h, RunningState.h, StoppingState.h
    //Context.h, Context.cpp
    CContext context;
    CCloseingState closeingState;
    context.SetLiftState(&closeingState);
    context.Close();
    context.Open();
    context.Run();
    context.Stop();
}

int _tmain(int argc, _TCHAR* argv[])
{
    cout << "----------使用模式之前----------" << endl;
    DoIt();
    cout << "----------使用模式之後----------" << endl;
    DoNew();

    _CrtSetDbgFlag(_CRTDBG_LEAK_CHECK_DF | _CRTDBG_ALLOC_MEM_DF);
    _CrtDumpMemoryLeaks();
    return 0;
}</span></span>
State狀態模式的優點:

(1).狀態模式將與特定狀態相關的行為區域性化,並且將不同狀態的行為分割開來。

(2).所有狀態相關的程式碼都存在於某個ConcereteState中,所以通過定義新的子類很容易地增加新的狀態和轉換。

(3).狀態模式通過把各種狀態轉移邏輯分不到State的子類之間,來減少相互間的依賴。

State狀態模式的缺點:

(1).在於使用狀態模式會增加系統類和物件的個數,且狀態模式的結構與實現都較為複雜,如果使用不當將導致程式結構和程式碼的混亂,對於可以切換狀態的狀態模式不滿足“開閉原則”的要求。導致較多的ConcreteState子類

State狀態模式使用總結:

      策略模式關注行為的變化,但歸根結底只有一個行為,變化的只是行為的實現.客戶不關注這些.當新增變化時對客戶可以沒有任何影響.狀態模式同樣關注行為的變化,但這個變化是由狀態來驅動,一般來說每個狀態和行為都不同.新增的狀態或行為一般與已有的不同,客戶需要關注這些變化.狀態模式中State及其子類中的操作都將Context傳入作為引數,以便State可以通過這個指標呼叫Context中的方法,修改狀態。而策略模式沒有。