1. 程式人生 > 實用技巧 >【設計模式】狀態模式

【設計模式】狀態模式

狀態模式

簡介

狀態模式將一個物件的狀態從物件中分離出來,封裝到專門的狀態類中,使得物件狀態可以靈活變化。對於客戶端而言,無需關心物件狀態的轉換以及物件所處的的當前狀態,無論處於何種狀態的物件,客戶端都可以一致處理。

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

結構

實現

實現方式:

  • 確定哪些類是上下文。它可能是包含依賴於狀態的程式碼的已有類;如果特定於狀態的程式碼分散在多個類中,那麼它可能是一個新的類。
  • 宣告狀態介面。雖然可能會需要完全複製上下文中宣告的所有方法,但最好是僅把關注點放在那些可能包含特定於狀態的行為的方法上。
  • 為每個實際狀態建立一個繼承於狀態介面的類。然後檢查上下文中的方法並將與特定狀態相關的所有程式碼抽取到新建的類中。
  • 在上下文類中新增一個狀態介面型別的引用成員變數,以及一個用於修改該成員變數值的公有設定器。
  • 再次檢查上下文中的方法,將空的條件語句替換為相應的狀態物件方法。
  • 為切換上下文狀態,需要建立某個狀態例項並將其傳遞給上下文。可以在上下文、各種狀態或客戶端中完成這項工作。無論在何處完成這項工作,該類都將依賴於其所例項化的具體類。
#include <iostream>
#include <typeinfo>

// 上下文
class Context;

// 狀態
class State {
protected:
    Context *m_context;

public:
    virtual ~State() {}
    void setContext(Context *context) {
        m_context = context;
    }
    virtual void handle1() = 0;
    virtual void handle2() = 0;
};

class Context {
private:
    State *m_state;

public:
    Context(State *state) : m_state(nullptr) {
        transitionTo(state);
    }
    ~Context() {
        delete m_state;
    }

    void transitionTo(State *state) {
        std::cout << "上下文:轉換狀態為 " << typeid(*state).name() << "\n";
        if(m_state != nullptr) {
            delete m_state;
        }
        m_state = state;
        m_state->setContext(this);
    }
    void request1() {
        m_state->handle1();
    }
    void request2() {
        m_state->handle2();
    }
};

// 具體狀態
class ConcreteStateA: public State {
public:
    void handle1() override;
    void handle2() override {
        std::cout << "狀態A:處理請求2。\n";
    }
};

// 具體狀態
class ConcreteStateB: public State {
public:
    void handle1() override {
        std::cout << "狀態B:處理請求1。\n";
    }
    void handle2() override {
        std::cout << "狀態B:處理請求2。\n";
        std::cout << "狀態B:想要更改上下文狀態。\n";
        m_context->transitionTo(new ConcreteStateA);
    }
};

void ConcreteStateA::handle1() {
    std::cout << "狀態A:處理請求1。\n";
        std::cout << "狀態A:想要更改上下文狀態。\n";
        m_context->transitionTo(new ConcreteStateB);
}

void ClientCode() {
    Context *context = new Context(new ConcreteStateA);
    context->request1();
    std::cout << "\n";
    context->request2();
    std::cout << "\n";
    context->request2();

    delete context;
}

int main(int argc, char *argv[]) {
    ClientCode();

    return 0;
}
# -*- coding: utf-8 -*-

from __future__ import annotations
from abc import ABC, abstractmethod


class Context(ABC):
    """
    """

    _state = None

    def __init__(self, state: State) -> None:
        self.transition_to(state)

    def transition_to(self, state: State):
        print(f"Context: Transition to {type(state).__name__}")
        self._state = state
        self._state.context = self

    def request1(self):
        self._state.handle1()

    def request2(self):
        self._state.handle2()


class State(ABC):
    """
    """

    @property
    def context(self) -> Context:
        return self._context

    @context.setter
    def context(self, context: Context) -> None:
        self._context = context

    @abstractmethod
    def handle1(self) -> None:
        pass

    @abstractmethod
    def handle2(self) -> None:
        pass


class ConcreteStateA(State):
    def handle1(self) -> None:
        print("ConcreteStateA handles request1.")
        print("ConcreteStateA wants to change the state of the context.")
        self.context.transition_to(ConcreteStateB())

    def handle2(self) -> None:
        print("ConcreteStateA handles request2.")


class ConcreteStateB(State):
    def handle1(self) -> None:
        print("ConcreteStateB handles request1.")

    def handle2(self) -> None:
        print("ConcreteStateB handles request2.")
        print("ConcreteStateB wants to change the state of the context.")
        self.context.transition_to(ConcreteStateA())


if __name__ == "__main__":
    # The client code.

    context = Context(ConcreteStateA())
    context.request1()
    print("\n")
    context.request2()
    print("\n")
    context.request2()

例項

問題描述

同上。

問題解答

同上。

總結

優點

  • 單一職責原則。將與特定狀態相關的程式碼放在單獨的類中。
  • 開閉原則。無需修改已有狀態類和上下文就能引入新狀態。
  • 通過消除臃腫的狀態機條件語句簡化上下文程式碼。

缺點

  • 如果狀態機只有很少的幾個狀態,或者很少發生改變,那麼應用該模式可能會顯得小題大作。

場景

  • 如果物件需要根據自身當前狀態進行不同行為,同時狀態的數量非常多且與狀態相關的程式碼會頻繁變更的話,可使用該模式。
  • 如果某個類需要根據成員變數的當前值改變自身行為,從而需要使用大量的條件語句時,可使用該模式。
  • 當相似狀態和基於條件的狀態機轉換中存在許多重複程式碼時,可使用該模式。

與其他模式的關係

  • 橋接模式狀態模式策略模式(某種程式上包括介面卡模式)的介面都非常相似,實際上,它們都基於組合模式,即將工作委派給其他物件,不過也都各自解決了不同的問題。
  • 狀態模式可被視為策略模式的擴充套件。兩者都基於組合機制:它們都通過將部分工作委派給“幫手”物件來改變其在不同情景下的行為。策略使得這些物件相互之間完全獨立,它們不知道其他物件的存在。但狀態模式沒有限制具體狀態之間的依賴,且允許它們自行改變在不同情景下的狀態。