多態
http://d.hatena.ne.jp/isshiki/20051209/p1
次の話がしたいだけな上にどうにも否定したいのでいい加減な説明ですが…
多態ってのはその、釈迦に説法かもしれませんが、汎用性の減った関数ポインタというか、そのかわりに記法がわかりやすくなるケースもあるというか、そんな感じのもので、オブジェクトが具体的に何者かわからんくてもとりあえず同じコンテナに叩き込んでおいて全部に同じメソッド呼んだらそれぞれ正しい動きをしてくれる…というかそんなものですかね。
まぁOO脳で作るとまずこんな感じになるかと思います。
class GameObject { virtual void move() =0; virtual void draw() =0; } class Shot : public GameObject { virtual void move() {} virtual void draw() {} } class Enemy : public GameObject { virtual void move() {} virtual void draw() {} } class ObejctMgr : public GameObject { virtual void move() { foreach(o; objs) o.move(); } virtual void draw() { foreach(o; objs) o.draw(); } private: GameObject objs; }
あとは omgr.move() を呼んでやれば勝手に Shot なら Shot の、 Enemy なら Enemy の move が呼ばれていってハッピー、とここまではOOマンセーなんですが、ここからがどうにも嬉しくなくて。ゲームってこんなに単純なものじゃなくて、例えばラジルギの剣で消える弾(WeakShot)と消えない弾(StrongShot)を用意しようと思うと、
class WeakShot : public Shot { virtual void hitSword() { die(); } } class GameObject { /* 以下のメソッドを追加する */ virtual void hitSword() { /* nothing */ } }
みたいなことをして、 omgr の全オブジェクトに対して hitSword を呼んでやれば良くて、まぁこれはこれで解決なんですけど、 WeakShot なんてたいした数無いのに全部のオブジェクトに対して hitSword を呼び出すのがどうにもこうにも気にいらなくて。
OOでラクに実装できるような問題ってのは、「全オブジェクトが全てのメッセージに対してなんらかのそれぞれ異なる応答をする」(例えば GameObject#move のような)か「オブジェクトの数がたいして無いから全オブジェクトに同じメソッドを呼んでもたいしてオーバヘッドにならない」(GUIなんかはそうだと思います)のどちらか、あるいは両方だと思うんですが、シューティングの場合は両方ともを満たさないと思うわけです。
それで、全オブジェクトを同じコンテナに詰めるのはあきらめて、せめて Shot だけを同じコンテナにつめて、 StrongShot#hitSword がムダに呼ばれるくらいはあきらめるくらいになるのかなーと思うんですけど、そんなこと考えて、シューティングってのはOOが素晴らしく働くドメインでは無いと信じて疑っていないわけです。
さらに、 hitSword だけならまだいいんですけど、次に例えば(あまり現実的な例じゃないですが)通常弾で消せる弾が剣で消せる弾と違うんなら、 hitShot とかも実装しなきゃいけなくて、「通常弾でも剣でも消せる弾」「通常弾のみで消せる弾」「剣のみで消せる弾」「消せない弾」の4つを用意しなきゃいけなくて、これはもちろん継承ツリーに表現することはできないわけで、結局 Shot っていうクラスだけ作ってこのへんはフラグで表現するかなー、とかになるかと思うわけです。そうなると結局古き良き switch - case 。
ちなみにOOと言えば他に出てくる、カプセル化については「ゲームってモジュール結合異常に強いからどうしても他のオブジェクトの中身知ってないとキツいよね」というようなことを思ってますがそれは余談。
Feature Base Object Management
そんで考えたのが以下のもの。上記は今考えた用語。要するによく話題になるオブジェクトはどのコンテナに入れる?問題。
以下のようなオブジェクト管理ができると嬉しいんじゃないかなーとか思ってるわけです。突然 Ruby っぽいコードになります。
class WeakWeakShot < Shot feature :hitSwordable, :hitShotable end class WeakStrongShot < Shot feature :hitSwordable end class StrongWeakShot < Shot feature :hitShotable end class StrongStrongShot < Shot end
とかいう感じでクラス単位で特徴を指示しておいて、あとは
omgr.each(:hitSwordable) {|o| o.hitSword}
とかでイテレーティングする、と。この際に全オブジェクトが :hitSwordable であるかどうかをチェックしてイテレーティングしてると比較大量発生でイヤンなわけですが、これはシステムがおさめるコンテナを勝手に階層化してくれればいいんじゃないかなと。
つまり、上記だと、 (Weak|Strong)(Weak|Strong)Shot の4つのクラスはそれぞれ別のコンテナ、例えば WeakWeakShotMgr におさめられて、かつ、勝手にシステムが作るクラス HitSwordableMgr は WeakWeakShotMgr と WeakStrongShotMgr をおさめていて、 each(:hitSwordable) とかすると、 HitSwordableMgr ごしに WeakWeakShotMgr と WeakStrongShotMgr をイテレートしてくれる、というような仕組みがあるといいのではないかなと。
あと、二種類の属性を使ってイテレートする場合、例えば :shot かつ :hitSwordable をイテレートしたい場合、 each(:shot, :hitSwordable) とかするとして、この場合はありえる全てのコンテナを作っておくと、 feature の数に対して指数オーダーのコンテナが必要になるので、こういう場合は :shot の条件を満たしたコンテナをなめてから、二つ目の条件に対してはコンテナを一つずつなめていけばいいかなと、オブジェクト全部を調べるのに比べればはるかに速いはず。
あーやっぱり説明が激しくわかりにくい。なんか今度実装して試してみます。というか SDL ネタにして意見聞いてみようとか思ってたわけですが。 C++ とかでこれやろうとするとなんかかなり面倒なことになりそうなので(最近の発想だとすぐに「動的にクラスを作るには…」とかになる) Ruby あたりでやってみようかと。
そして AOP の話とかもからめるつもりがからまってない。あと Composite パターンとか Strategy パターンとか MVC がめんどくさい話とか。