状態管理用の変数をインスタンスに持たせるなこのタコって話

たとえば、今、「ユーザーが方向を入力したらプレイヤーが動くゲーム作りたい」みたいなはなしがあるとする。その場合、モデルクラスはまあシンプルな実装として下のようなものが考えられると思う。

「できたよー」って見せにいったら、今度は「あのさー、『高速移動モード』っていうモード欲しいんだよね。そのモードだと二倍速で動くの」って言われたとする。シンプルにやるとこうなりますね。

「できたよー」って見せにいったら、今度は「なあ、すげえ面白いこと考えたんだけど、『蟹モード』って面白くない?横は4倍速で動くんだけど縦は半分の速度で動くの」とか言われたわけです。あなたは「お、おう」と言って、以下のようにコードを修正しました。

これ、ヤバい感じしますね。破滅の匂いがする。「今度は『よっぱらいモード』欲しいな〜。入力に関係なくランダムに動くの」みたいなこと言われたら確実に複雑さが爆発してメンテ不能になり鬱になり死んでしまう。そしてあとに残されたおそろしいコードを引き継いだ人間が、次なる被害者に……………………。という未来まで見えてきたかと思います。

こういう風に、状態変数みたいなやつをインスタンスに持たせて、「状態でswitch caseして処理をわけよーっと」みたいなことすると、死人のパレードが起こることが多い。じゃあどうするか。先人は偉大です。困ったときには先人の知恵デザインパターンをひもといてみましょう。次の瞬間あなたは叫びます。「アッこれStateパターンでやったところだ!」進研ゼミに感謝ですね。Stateパターンを適用すると、こんな感じになります。

こうやって、自分で状態変数を見てswitch caseして全ての条件の振る舞いを面倒みるんじゃなくて、それぞれの条件の振る舞いのみに関心を持つクラスを、同じインターフェイスで作ってあげて(今回の例、JavaならMovableインターフェイスとか作ってそれぞれのクラスはそれをimplementsすればいいと思うよ。で、moving_modeインスタンス変数の型はMovableにすればいいね!)、そいつに「じゃ、あとよろしく〜〜〜」って処理を丸投げすると嬉しいことになります。

余談だけど、こういう、「別のクラスのインスタンスを保持しておいて、そいつに処理をお願いしちゃう」みたいなやり方を「委譲」と言って、「継承」よりもずっとずっと大事な概念なので覚えておくと吉です。

さて、とりあえずここまで書いたあなたは、ディレクターに「これでどうや!!!」と見せに行きました。そしたらディレクターは今度は「首寝違えモードっておもしろくない? 上を入力したら左に、左を入力したら下に、下を入力したら右に、右を入力したら上に行くの!!!!」とわけのわからないことを言い始めました。まったく、いつの世もディレクターってやつは勝手ですね。リファクタリング前の状態でこんなこと言われたら、あなたはおそらく激怒して「てめー!これ以上勝手なこと抜かすとどうなるかわかんねーぞ(おもに自分の精神が)!!!」みたいなことをわめき散らかしてしまうことでしょう。するとディレクターは「えっこんなの簡単じゃないの?」とプログラマーをイラつかせる台詞ナンバー1みたいなことを言ってあなたを「使えねー」みたいな目で見るのでした……。しかし、もしもこれがリファクタリング後のコードだったらどうでしょう? あなたは「ちょっと待ってろ」と言ってエディタを開いて、なんなくコードを修正するのでした。

これでもうあなたは、「倍速モードのやつさー、そのモードに入るときに2倍速だけじゃなくて3倍、4倍とかにもなれるようにできない?」とか言われても怖くないし、「上押すと右斜め上に動くモード作ってよ」とか言われても怖くない!!!

Stateパターン、便利ですね。というわけで、リピートアフターミー。「状態管理用の変数をインスタンスに持たせるなこのタコ」

追記

Stateパターンでは、状態遷移をどうやって実装するかまではパターン化されてないので、そのあたりはケースによって工夫するといいですね。今回は to_kani_mode みたいな感じでpublicメソッドでやってるけど、その他にもいろんな方法で状態遷移の管理はできると思います。

追記の追記

当然ながら、状態管理用の変数を用いて十分にシンプルな実装ができているなら、こんなふうにする必要はないと思います。シンプルな状態をわざわざ複雑にする必要はありません。状態管理用の変数のせいで複雑怪奇なメソッドになってきたな……と思ったら、そういうときにはStateパターンを使うとシンプルにできることが多いよ、という話だと思ってください

気になった反応に反応しました。よろしければこちらもどうぞ

http://nekogata.hatenablog.com/entry/2013/02/11/110304

追記の追記の追記

Strategyパターンじゃね?って声がありますが、わたしの認識としてはStateパターンはStrategyパターンの部分集合です。オブジェクトの状態によってstrategyを変えるパターンがStateパターンと認識しています。

今回は要求になかったですが、「高速モードは4ターンしかもたなくて、5ターン目からはノーマルにもどりたいんだよねー」みたいなふうになったときには、高速移動モードのstateオブジェクトに「現在何ターン目か」という情報を保持しておいて、それが4になったらplayerのstateをnormalに変化させる(そのためにはstateオブジェクトがなんらかのかたちでplayerオブジェクトを保持しておく必要もありますね)みたいなふうにすると良いと思います。

つまり、なにが言いたいかと言うと、

  • 多くの場合状態遷移の責務は状態オブジェクトに持たせたほうがコードはクリーンになりますし、そこまで含めてStateパターンの定石ではあると思います。
  • ただし、上記の通りStateパターンでは状態遷移をどうやって実装するかまではパターン化されていなかったと思います。