検索ガイド -Search Guide-

単語と単語を空白で区切ることで AND 検索になります。
例: python デコレータ ('python' と 'デコレータ' 両方を含む記事を検索します)
単語の前に '-' を付けることで NOT 検索になります。
例: python -デコレータ ('python' は含むが 'デコレータ' は含まない記事を検索します)
" (ダブルクオート) で語句を囲むことで 完全一致検索になります。
例: "python data" 実装 ('python data' と '実装' 両方を含む記事を検索します。'python data 実装' の検索とは異なります。)
practical_python_design_patterns

Practical Python Design Patterns - Python で学ぶデザインパターン: The State Pattern part. 1「第15章: ステートパターン」の巻 投稿一覧へ戻る

Published 2022年6月30日20:11 by mootaro23

SUPPORT UKRAINE

- Your indifference to the act of cruelty can thrive rogue nations like Russia -

Practical Python Design Patterns - The State Pattern 編

Chapter 15: State Pattern
(第15章: ステートパターン)

Under pressure.
- Queen, "Under Pressure"
Overview
(概要)
ソフトウェアにおける問題を考える際に非常に役立つツールとして状態遷移図 (state transition diagram) があります。状態遷移図では1つ1つのノードがその時々のシステムの状態を表し、あるノードから他のノードへの状態の遷移を矢印が表現しており、ある入力が与えられたときにシステムがどういった状態をとるか、ということを視覚的に捉えることができます。また、システムがある状態から次の状態へ遷移する方法を考えるうえでも役に立ちます。
このシリーズではゲームの作成について取り上げたことがありますが、ここでもプレイヤーのキャラクターについて「状態遷移」というものを考えてみましょう。キャラクターの最初 (デフォルト) の状態は「立っている」状態としましょう。左、もしくは、右の矢印キーを押すと、それぞれの方向へ移動する、という状態に移行します。上矢印キーの押下は現在の状態にかかわらずキャラクターをジャンプさせます。一方下矢印キーの押下はかがんでいる姿勢を取らせます。当然ですが、上矢印キーが押されてジャンプしたキャラクターは元の状態へ戻るわけです (まぁ、空を飛べるとなると話は違ってきますが...)。しかしこの時上矢印キーがリリースされず押されっ放しであれば着地と同時に再びジャンプすることになります。そして、どのキーであってもリリースされればどのような状態であったとしても最初の状態、すなわち「立っている」状態へ戻ります。
それほどいい例ではありませんが、それでも状態遷移図についての朧げなイメージは持っていただけたのではないかと思います。
もう少し「かしこまった」例で再度考えてみましょう。ATM マシーンが取り得る状態を非常に単純化すると次のようになるでしょうか:
待機中
カードの受け付け
PIN の入力
PIN の検証
PIN がエラー時の対処
操作内容の選択
各操作における一連の処理に関わる諸々の状態
取引の終了
カードの返却
取引内容シートの印刷
取引内容シートの発行
ユーザーのアクションやシステムにおける処理の結果などから、ATM はある状態から次の状態へと移行します。例えば、ATM にカードを挿入することで、状態は「待機中」から「カードの受け付け」へと移行する、といった具合です。
一旦状態遷移図を書き終えてしまえば、それぞれのステップにおいてシステムが実行すべき操作内容を極めて明確に思い描くことができるはずです。また、ある状態から次に移行すべき状態についてのアイデアもはっきりするはずです。この段階まで辿り着けば、あとは状態遷移図をコードで表現する、という作業が残っているだけです。
状態遷移図から実行可能なコードを書き起こす最も単純な方法は、状態を保持する全体 (state machine) を象徴するオブジェクトを定義することです。そのオブジェクトではそれぞれの状態を属性として保持し、それらによって「入力」に対する「反応」を決定することになります。最初のゲームキャラクターを象徴するクラスを考えた場合次のようになるでしょう:
Note
以下の例では端末制御のために curses ライブラリを使用していますが Windows 上では動作しません。試してみたい方は Windows 上に Linux 様のターミナル環境を実現する Cygwin などをインストールしてください。
import time
import curses

def main():
win = curses.initscr()
curses.start_color()
curses.noecho()
curses.cbreak()

win.addstr(0, 0, "w a s d キーでプレイヤーをコントロールします")
win.addstr(1, 0, "終了するには x を押します")
win.addstr(2, 0, "> ")

curses.init_pair(1, curses.COLOR_YELLOW, curses.COLOR_RED)
curses.init_pair(2, curses.COLOR_CYAN, curses.COLOR_MAGENTA)
current_row = 2
win.addstr(current_row, 0, "> ")
while True:
ch = win.getch()
if ch is not None:
if ch == 120:
break
elif ch == 97: # a
win.addstr("Running Left", curses.color_pair(2))
elif ch == 100: # d
win.addstr("Running Right", curses.A_BOLD)
elif ch == 119: # w
win.addstr("Jumping", curses.A_DIM)
elif ch == 115: # s
win.addstr("Crouching", curses.A_REVERSE)
else:
win.addstr("Standing", curses.color_pair(1))
current_row += 1
win.addstr(current_row, 0, "> ")
time.sleep(0.05)
curses.endwin()

if __name__ == "__main__":
main()
practical_python_design_patterns_state_pattern_chapter_15_1_curses
Curses ライブラリを使用したキーボード入力に対する反応例
興味のある方は pygame モジュールを利用してこのコードを書き換えてみてください (pygame モジュールについては 「Practical Python Design Patterns - Python で学ぶデザインパターン: The Factory Pattern - Part. 1 「第4章: ファクトリーパターン - PyGame でグラフィックスを描画してみよう」の巻」で少しだけ取り上げています)。curses ライブラリを使用した文字列が表示されるだけのこの例よりも非常に興味深いものになるはずです。この際 pygame の sprite モジュールを利用してファイルから読み込んだ画像を表示してみてください。自分が作成したキャラクターが画面内でジャンプしたり走り回ったりします; 想像するだけでワクワクしますね!! また、sprite モジュールでは「当たり判定」機能も提供されています。いよいよゲームプログラマーの仲間入りです!!!
ちょっと興奮しちゃいました、話題を戻しましょう。さて、この curses ライブラリを利用して記述したコードを見て「危険な香り」をちゃんと感じているでしょうか?その根源は、入力に応じて処理を振り分けている if 文の存在です。状態遷移図が象徴しているのはオブジェクト指向なシステムであり、それを基にした実装 (state machines の作成) は広く行われていることですから、こういった「危険な香り」を放つコードをクリーンに保つためのデザインパターンの存在も当然予想できることです。それがこの章で取り上げるステートパターン (state pattern) です。