読者です 読者をやめる 読者になる 読者になる

多態

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と言えば他に出てくる、カプセル化については「ゲームってモジュール結合異常に強いからどうしても他のオブジェクトの中身知ってないとキツいよね」というようなことを思ってますがそれは余談。

なにかあれば下記メールアドレスへ。
shinichiro.hamaji _at_ gmail.com
shinichiro.h