1. 程式人生 > >設計模式 c++版(19)—— 狀態模式

設計模式 c++版(19)—— 狀態模式

定義:
當一個物件內在狀態改變時允許其改變行為,這個物件看起來像改變了其類。(狀態的變更引起了行為的變更,從外部看起來好像這個物件對應的類發生了改變一樣)

示例一:狀態模式(通用版)


1. 類圖 26-5

2. 類圖說明

State 抽象狀態角色
介面或抽象類,負責物件狀態定義,並且封裝環境角色以實現狀態切換。

ConcreteState 具體狀態角色
每一個具體狀態必須完成兩個職責:本狀態的行為管理以及趨向狀態處理,即本狀態要做的事情,以及本狀態如何過度到其他狀態。

Context 環境角色
定義客戶端需要的介面,並且負責具體狀態的切換。


3. 程式碼清單
 

#include <QCoreApplication>
#include <QDebug>

class Context;
//抽象環境角色
class State
{
public:
    void    setContext(Context &context)
    {
        this->m_context = &context;
    }
    virtual void    handle1()   = 0;
    virtual void    handle2()   = 0;

protected:
    Context *m_context;
};

//具體環境角色
class Context
{
public:
    State*  getCurrentState()
    {
        return this->m_curState;
    }
    void    setCurrentState(State* state)
    {
        this->m_curState = state;
        this->m_curState->setContext(*this);
    }
    void    handle1()
    {
        this->m_curState->handle1();
    }
    void    handle2()
    {
        this->m_curState->handle2();
    }

public:
    static State* m_state1;
    static State* m_state2;
private:
    State*  m_curState;
};

//環境角色
class State1:public State
{
public:
    virtual void    handle1()
    {
        qDebug() << "this is handle1";
    }
    virtual void    handle2()
    {
        this->m_context->setCurrentState(this->m_context->m_state2);
        this->m_context->handle2();
    }
};

class State2:public State
{
public:
    virtual void    handle1()
    {
        this->m_context->setCurrentState(this->m_context->m_state1);
        this->m_context->handle1();
    }
    virtual void    handle2()
    {
        qDebug() << "this is handle2";
    }
};

State* Context::m_state1 = new State1();
State* Context::m_state2 = new State2();

int main()
{
    Context context;
    State* state1 = new State1();
    context.setCurrentState(state1);
    context.handle1();
    context.handle2();
    
    delete state1;
    
    return 0;
}


示例二:電梯

1. 類圖 26-1

2. 分析特定狀態

  •  敞門狀態。這時可以關門
  •  閉門狀態。這時可以開門,停止,執行
  •  執行狀態。這時只能停止
  •  停止狀態。這時可以執行和開門

3. 電梯狀態和動作之間關係圖 26-2

4. 擴充套件狀態後的類圖 26-3

5. 擴充套件狀態後代碼
 


//電梯介面
class ILift
{
public:
    virtual void    open()  = 0;
    virtual void    close() = 0;
    virtual void    run()   = 0;
    virtual void    stop()  = 0;
    virtual void    setState(int state)  = 0;
    
public:
    static const int OPENING_STATE  = 1;
    static const int CLOSING_STATE  = 2;
    static const int RUNNING_STATE  = 3;
    static const int STOPPING_STATE = 4;
};

//電梯實現
class Lift:public ILift
{
public:
    virtual void    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;
        }  
    }
    virtual void    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;
        }  
    }
    virtual void    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;
        } 
    }
    virtual void    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;
        }  
    }
    virtual void    setState(int state)
    {
        this->m_state = state;
    }
    
private:
    void    closeWithoutLogic()
    {
        qDebug() << "close";
    }
    void    openWithoutLogic()
    {
        qDebug() << "open";
    }
    void    runWithoutLogic()
    {
        qDebug() << "run";
    }
    void    stopWithoutLogic()
    {
        qDebug() << "stop";
    }

private:
    int     m_state;
};

int main()
{
    ILift *lift = new Lift();
    lift->setState(ILift::STOPPING_STATE);
    lift->open();
    lift->close();
    lift->run();
    lift->stop();
    
    delete lift;
    return 0;
}

6. 存在的問題

 電梯實現類Lift有點長。裡面使用了大量的switch...case,在業務複雜的情況下,程式會更長。
 擴充套件性非常差。電梯還有通電和斷點兩個狀態,若要增加這兩個方法,原有4個方法都需要增加判斷條件
 非常規狀態無法實現。

7. 使用狀態模式,類圖 26-4

8. 使用狀態模式,程式碼

/ ******************************************* 宣告 ********************************* ///

class LiftState;
//上下文類  宣告
class Context
{
public:
    Context();
    LiftState*  getLiftState();
    void        setLiftState(LiftState *liftState);
    void        open();
    void        close();
    void        run();
    void        stop();
    
public:
    static LiftState *m_openningState;
    static LiftState *m_closingState;
    static LiftState *m_runningState;
    static LiftState *m_stoppingState;
    
private:
    LiftState *m_liftState;
};

//抽象電梯狀態 宣告
class LiftState
{
public:
    void            setContext(Context &context);
    virtual void    open()  = 0;
    virtual void    close() = 0;
    virtual void    run()   = 0;
    virtual void    stop()  = 0;

protected:
    Context *m_context;
};

//關閉狀態 宣告
class ClosingState:public LiftState
{
public:
    virtual void    open();
    virtual void    close();
    virtual void    run();
    virtual void    stop();
};

//敞門狀態 宣告
class OpenningState:public LiftState
{
public:
    virtual void    open();
    virtual void    close();
    virtual void    run();
    virtual void    stop();
};

//執行狀態 宣告
class RunningState:public LiftState
{
public:
    virtual void    open();
    virtual void    close();
    virtual void    run();
    virtual void    stop();
};

//停止狀態 宣告
class StoppingState:public LiftState
{
public:
    virtual void    open();
    virtual void    close();
    virtual void    run();
    virtual void    stop();
};

/// *********************************************** 定義 ************************************************* ///

//上下文類  定義
LiftState* Context::m_openningState = new OpenningState();
LiftState* Context::m_closingState = new ClosingState();
LiftState* Context::m_runningState = new RunningState();
LiftState* Context::m_stoppingState = new StoppingState();

Context::Context()
{
    this->setLiftState(this->m_closingState);
}

LiftState* Context::getLiftState()
{
    return this->m_liftState;
}
void    Context::setLiftState(LiftState* liftState)
{
    this->m_liftState = liftState;
    this->m_liftState->setContext(*this);
}
void    Context::open()
{
    this->m_liftState->open();
}
void    Context::close()
{
    this->m_liftState->close();
}
void    Context::run()
{
    this->m_liftState->run();
}
void    Context::stop()
{
    this->m_liftState->stop();
}

//抽象電梯狀態 定義
void LiftState::setContext(Context &context)
{
    this->m_context = &context;
}

//關閉狀態 定義
void    ClosingState::open()
{
    this->m_context->setLiftState(m_context->m_openningState);
    this->m_context->getLiftState()->open();
}
void    ClosingState::close()
{
    qDebug() << "close";
}
void    ClosingState::run()
{
    this->m_context->setLiftState(m_context->m_runningState);
    this->m_context->getLiftState()->run();
}
void    ClosingState::stop()
{
    this->m_context->setLiftState(m_context->m_stoppingState);
    this->m_context->getLiftState()->stop();
}


//敞門狀態 定義
void    OpenningState::open()
{
    qDebug() << "open";
}
void    OpenningState::close()
{
    this->m_context->setLiftState(m_context->m_closingState);
    this->m_context->getLiftState()->close();
}
void    OpenningState::run(){}
void    OpenningState::stop(){}

//執行狀態 定義
void    RunningState::open(){}
void    RunningState::close(){}

void    RunningState::run()
{
    qDebug() << "run";
}
void    RunningState::stop()
{
    this->m_context->setLiftState(m_context->m_stoppingState);
    this->m_context->getLiftState()->stop();
}

//停止狀態 定義
void    StoppingState::open()
{
    this->m_context->setLiftState(m_context->m_openningState);
    this->m_context->getLiftState()->open();
}
void    StoppingState::close(){}
void    StoppingState::run()
{
    this->m_context->setLiftState(m_context->m_runningState);
    this->m_context->getLiftState()->run();
}
void    StoppingState::stop()
{
    qDebug() << "stop";
}


int main()
{
    Context context;   
    context.open();
    context.close();
    context.run();
    context.stop();
    
    return 0;
}


三、狀態模式的應用

1. 優點:

  •  結構清晰。避免了過多的條件巢狀導致的程式的複雜性,提高可維護性。
  •  遵循設計原則。很好地體現了開閉原則和單一職責原則,每個狀態都是一個子類,要增加狀態就要增加子類,需要修改狀態,只修改一個子類就好
  •  封裝性好。狀態變換放置到類的內部實現,外部的呼叫不用知道類內部如何實現狀態和行為的變換。

2. 缺點:

子類會太多,也就是類膨脹。如果完全使用狀態模式就會有太多的子類,不好管理,需要在專案中自己衡量。有很多方式可以解決這個狀態問題,如在資料庫中建立一個狀態表,然後根據狀態執行響應的操作。


3. 使用場景:

  •  行為隨狀態改變而改變的場景。例:許可權設計,人員的狀態不同即使執行相同的行為結果也會不同,在這種情況下需要考慮使用狀態模式。
  •  條件、分支判斷語句的替代者。在程式中大量使用switch或if潘丹語句會導致程式結構不清晰,邏輯混亂。

4. 注意事項:

 狀態模式適用於當前某個物件在它的狀態發生改變時,它的行為也隨著發生比較大的變化,也就是說在行為受狀態約束的情況下可以使用狀態模式,而且使用時物件的狀態最好不要超過5個。

參考文獻《秦小波. 設計模式之禪》(第2版) (華章原創精品) 機械工業出版社