オブジェクトにアルゴリズムを持たせてはいけない。そんなことしたら、そのアルゴリズムが他で使えなくなってしまう。
アルゴリズムが求めるオブジェクトの特性を抜き出して、それとこれをアレ。
やべー若くないのに似たようなことをいまさらやっている。Actorを継承したTokenがTokenStateとTokenSpecを持っていてTokenSpecにアルゴリズムを抜き出す、とかいう作業。Tokenっていう名前が適切かどうかはよく分からんが。
先日言及した(id:ABA:20060610#p1)friendとwithもこの辺で使っていて、この例で言うところのSpaceShipのフィールドをprivateに保った上でKabeMoveとかから参照する際にfriendにするとか、わざわざ'o.'って書くのが面倒だからwithで済ますとか、そういったところだ。
あとこれは
void kaze_no_naka_wo_move()
もはや詩だね。NihongoTtteKakkoii!(Think)。
さらに続きがあるな……
- 移動アルゴリズムは移動できるオブジェクト全てに使えるべきだ!!(id:w_o:20060613#p2)
- データとかアルゴリズムとかポリシーとかの分離とか(id:shinichiro_h:20060614#1150218311)
はいはいOO脳OO脳(id:shinichiro_h:20051210#1134181805)。こういったところに凝ることは、可読性が向上するかどうかは微妙だしゲームの面白さにはびた一文寄与しないんだけど、プログラマの自己満足を満たすという点ではすばらしく、私もこういった作りこみをニヤニヤしながらやっていまう。OO脳ゲームプログラマの自己満足を満たす楽しいニヤニヤOOPゲーム開発指南、とかいう後ろ向きな企画、だれかやらない?
ちなみに現状使っているTokenはこんな感じ。Actorはいつものabagames.util.actorモジュールのやつ。Token, TokenState, TokenSpecを継承したクラスとそのTokenのActorPoolを作って利用する。initとかsetとかmoveとかは必要なら適当にオーバーライド。
public class Token(ST, SP): Actor { protected: ST state; SP spec; public void init(Object[] args) { state = new ST; } public void set(SP spec, float x, float y, float deg, float speed) { this.spec = spec; set(x, y, deg, speed); } public void set(float x, float y, float deg, float speed) { state.clear(); state.pos.x = x; state.pos.y = y; state.deg = deg; state.speed = speed; spec.set(state); _exists = true; } public void move() { if (!spec.move(state)) remove(); } public void remove() { _exists = false; spec.removed(state); } public void draw() { spec.draw(state); } public Vector pos() { return state.pos; } } public class TokenState { protected: bool isInitialized = false; Vector pos; float deg; float speed; invariant { if (isInitialized) { assert(pos.x <>= 0); assert(pos.y <>= 0); assert(deg <>= 0); assert(speed <>= 0); } } public this() { pos = new Vector; } public void clear() { pos.x = pos.y = 0; deg = 0; speed = 0; isInitialized = true; } public void stepForward() { pos.x -= sin(deg) * speed; pos.y += cos(deg) * speed; } } public class TokenSpec(T) { protected: Shape shape; public void set(T state) {} public void removed(T state) {} public bool move(T state) { return true; } public void draw(T state) { with (state) { shape.draw(pos, deg); } } } public interface Shape { public void draw(Vector pos, float deg); }
移動アルゴリズム分離という意味では全然甘いのだが、こんなもんでだいたい困らない。あーでも以下の点がダサい。
- 移動をspeed, degでなくてXYの速度(Vector velocity)とかで表す時の書き方がダサくなる。ぞれぞれの方式をmixinで織り込んだ上で適当なプロパティを生成すればいいんだろうけど、めんどい
- Shape.drawの引数が固定なのがダサい。あーでもこれはstateそのまま渡せばいいか
- あらゆる引数にstate丸投げなのがダサい、か?引数に情報量がないよな
- Token.setの引数が固定なのもダサい。TokenSpec.setがあまり意味ない
- TokenState内のフィールドをfriendで参照した上でwithでごまかしているのがダサい。TokenSpecとTokenStateに同名のフィールドがあったときに困る
ちなみにダサさと可読性の間にはあんまり相関関係はないような印象。じゃあOO的にかっこいいってのは、何が評価軸なんだろ?