stateパターン
stateパターンってどうなんだろうと前から思っている。
C++でゲームのコードを考える時によく出てくるように思うけれど
結局switchやifが消えてないように思うし、それぞれの子クラスが大きくならないか?って思う。
かといって子クラスがある程度肥大化するたびに共通部分を切り出して親クラスの共通処理に書くのも限度がある。
stateパターンを継承した子クラスは、遷移しうる状態の集まりのうちの1つであるはずだけれど、
本当にすべての状態が持つべき共通の性質ってそんなにあるんだろうか。
とりあえず最低限は今自分の状態が何で、次の状態が何で、その2つが違うかどうかがわかる実装を前書いたなぁ。
- 初期化時には今==次
- 今==次なら切り替えないで維持
- 今!=次なら切り替える
- state内部で切り替えるべき条件が揃ったら’次’を今ではなくて次の遷移先にする
オーバーロードとかで分岐したほうがきれいなんだろうか。
そして子クラス間でデータを共有する方法も良くわからないし。 Data型を継承したData子クラスとかでうまいことできるんだろうか?
StateAとStateBがそれぞれInputDataA/OutPutDataA, InputDataB/OutputDataBで外部とやり取りするようなことを考えると
StateA->StateBのデータの受け渡しでは
StateA->OutPutDataA->InputDataB->StateB のような流れになるのだと思う。
ここでStateA->StateCのような経路のやり取りを足すとなると同様に
StateA->OutPutDataA->InputDataC->StateC が増える。
結局これって
"「すべてのState」が持ちうるデータの和集合をStateの分岐をするマネージャクラスあたりに置いておいて、
そこからStateのsetterやctorで入れてやる”
ような方法をわざわざ複雑怪奇にしたもののように思える。
これを簡潔にやれるようにしたのがDIコンテナというものではないのか?知らんけど。
DIコンテナみたいなことはC++だとJavaやC#でいうreflectionがなかったりで面倒な印象がある。
とりあえずシンプルで愚直なstateパターンを書いた。
これくらいの差異しかないと継承ではなくctorに別の引数を与えたインスタンスでもいいけど。
今日は時間がないのでここまでにして、今後ここから考えてみる。
Haskellみたいに状態をpair<状態, 次の状態を得る関数> (?)で管理するのも面白そうだけど、そのためにもなるべく状態そのものを小さく扱ったほうが良いように感じるんだよなぁ。難しい。
#include<iostream> #include<memory> #include<string> namespace my { class state { private: const std::string name_; public: state( const std::string& name_ ) :name_{ name_ }{} auto name()const{ return name_; } virtual void exec()const { std::cout << this->name() << "\n"; }; virtual ~state(){} }; class initial :public state { private: using super = state; public: initial() : super{ "initial" }{} }; class running :public state { private: using super = state; public: running() :super{ "running" }{} }; class exit :public state { private: using super = state; public: exit() :super{ "exit" }{} }; } int main(){ using my::state; using my::initial; using my::running; using my::exit; std::unique_ptr<state> sp{}; sp.reset( new initial{} ); while( sp != nullptr && sp->name() != "exit" ) { sp->exec(); if( sp->name() == "initial" ) sp.reset( new running{} ); else if( sp->name() == "running" ) sp.reset( new exit{} ); } sp->exec(); sp.reset(); std::cout << "finished all\n"; return 0; }