doc.dev1x.org

State

1. 目的

状態に応じて実行するコードを分割したい

2. 課題

条件分岐で状態ごとのコードを記述すると長大になりがち

class Program:

    def exec(self, status):
        if status == "status01":
            # 以下、10-20行程度の処理が書いてあるものとする
            ...
            return "わーい"
        elif status == "status02":
            # 以下、10-20行程度の処理が書いてあるものとする
            ...
            return "うぇーい"
        else:
            raise ValueError("未定義のstatus")

if __name__ == '__main__':
    p = Program()
    print(p.exec("status01"))     # <= わーい
    print(p.exec("status02"))     # <= うぇーい

3. 解決策

処理を委譲するクラスを切り替えられるようにする

class Context:
    def change_state(self, state):
    """ stateを変更する """
        self._state = state

    def exec(self):
    """ stateに委譲した処理を呼び出す """
        return self._state.exec()

class State(metaclass=ABCMeta):
    """ 処理を委譲されるクラスのインターフェイス """
    @abstractmethod
    def exec(self):
        pass

class StateA(State):
    """ 処理を委譲されるクラスの実装その1 """
    def exec(self):
        # 以下10-20行程度の処理が書いてあるものとする
        ...
        return "わーい"

class StateB(State):
    """ 処理を委譲されるクラスの実装その2 """
    def exec(self):
        # 以下10-20行程度の処理が書いてあるものとする
        ...
        return "うぇーい"


if __name__ == '__main__':
    c = Context()
    c.change_state(StateA())
    print(c.exec())     # <= わーい
    c.change_state(StateB())
    print(c.exec())     # <= うぇーい

状態を判定する条件分岐はFactoryに隠蔽する

class State(metaclass=ABCMeta):
    # 先に示したコードと同じなのでここでは省略

class StateA(State):
    # 先に示したコードと同じなのでここでは省略

class StateB(State):
    # 先に示したコードと同じなのでここでは省略

class StateFactory:
    @staticmethod
    def create(status):
        if status == "status01":
            return StateA()
        elif status == "status02":
            return StateB()
        else:
            raise ValueError("未定義のstatus")


if __name__ == '__main__':
    c = Context()
    state = StateFactory.create("status01")
    c.change_state(state)
    print(c.exec())     # <= わーい
    state = StateFactory.create("status02")
    c.change_state(state)
    print(c.exec())     # <= うぇーい

4. メリット

状態依存の処理を分離できる

5. デメリット

コード量が増える

6. 注意事項

WEBアプリ/APIでは利用することはあまりない

StateパターンとStrategyパターンの違い

参考資料

Appendix-1 Contextクラスに状態判定をさせるパターン

class Context:
    def change_state(self, status):
    """ stateを変更する """
        self._state = StateFactory.create(status)

    def exec(self):
    """ stateに委譲した処理を呼び出す """
        return self._state.exec()

class State(metaclass=ABCMeta):
    # 先に示したコードと同じなのでここでは省略

class StateA(State):
    # 先に示したコードと同じなのでここでは省略

class StateB(State):
    # 先に示したコードと同じなのでここでは省略

class StateFactory:
    # 先に示したコードと同じなのでここでは省略


if __name__ == '__main__':
    c = Context()
    c.change_state("status01")
    print(c.exec())     # <= わーい
    c.change_state("status02")
    print(c.exec())     # <= うぇーい