前の記事 で「 リフレクション を使えば マルチメソッド(Multimethods) を比較的簡単に実現できそう…」ということを書いたので(マルチメソッドは多重ディスパッチ(Multiple Dispatch)などとも呼ばれます)、C# でちょっくらやってみました。



class Spaceship { }

class Asteroid { }

...


class Space // あるいは partial class にした方が…

{

 public static void DoCollide(Asteroid one, Asteroid another)

 {

  Debug.Print("小惑星と小惑星が衝突");

 }

 public static void DoCollide(Asteroid one, Spaceship another)

 {

  Debug.Print("小惑星と宇宙船が衝突");

 }

 public static void DoCollide(Spaceship one, Asteroid another)

 {

  Debug.Print("宇宙船と小惑星が衝突");

 }

 public static void DoCollide(Spaceship one, Spaceship another)

 {

  Debug.Print("宇宙船と宇宙船が衝突");

 }

 public static void DoCollide(Spaceship one, object another)

 {

  Debug.Print("宇宙船とUnknownが衝突");

 }

 … 省略 …

 public static void DoCollide(object one, object another)

 {

  Debug.Print("UnknownとUnknownが衝突");

 }

 public static void Collide(object a, object b)

 { // 実行時の型に基づいて DoCollide メソッドを呼び分けているところ

  typeof(Space).GetMethod(

    "DoCollide",

    new Type[]{a.GetType(), b.GetType()} // ここがポイント

   ).Invoke(null, new object[]{a, b});

 }

}


class Program

{

 static void Main(string[] args)

 {

  object[] o = new object[] { new Spaceship(), new Asteroid(),...

  Random randome = new Random();

  for (int i = 0; i < 10; ++i)

  {

   Space.Collide(

    o[randome.Next(o.Length)], o[randome.Next(o.Length)]);

  }

 }

}



実際にサンプルプログラムを動かしてみると、ちゃんと2つのオブジェクトの実行時の型に基づいてメソッドが呼び分けられていることがわかると思います(この例はダブルディスパッチになっています)。上の例では Space クラスの Collide や DoCollide メソッドを static にしていますが、これを非 static にすることもできます(その場合はトリプルディスパッチになります)。


さて、注目すべきは Spaceship クラスと Asteroid クラスの間になにも依存関係がないことです。これはとても重要なことです。もし、


spaceship.Collide(asteroid);

asteroid.Collide(spaceship);


などと書いてしまうと Spaceship クラスと Asteroid クラス間に依存関係を持ち込んでしまうことになります。また、宇宙船-小惑星間の衝突と小惑星-宇宙船間の衝突を2つのクラスにまたがって書かなければならなくなります。これは衝突という関心事をソースコード上の遠くはなれたところに分散させて書かなければならないことを意味します(もっとも、C# 2.0 以降であれば部分型(partial)を使ってコードを1箇所にまとめられないこともないですが)。さらに別の物体と宇宙船との衝突を考えなければならなくなると、Spaceship クラスにメソッドを追加して、また別の依存関係を持ち込まなければならなくなってしまいます。そうやって、Spaceship クラスはどんどん肥大化し、たくさんのクラスと関係を持たなければならなくなるでしょう。そういう実装はできれば避けたいところです。


多重ディスパッチと関連するデザインパターンに Visitor パターンがあります。Visitor パターンではダブルディスパッチを使います。しかし、ダブルディスパッチを直接使うことができれば Visitor パターンを使わなくても(すなわち設計を複雑にしなくても)、やりたいことを実現できる場合が多いみたいです(多重ディスパッチが使える言語では Visitor を使う機会はすくないそうです)。


ところで、C++ で多重ディスパッチを実装しようとした場合、なかなかこう簡単にはいきませんよね(書籍『More Effective C++』や『Modern C++ Design』などに実装方法が載っています)。



追記(2008/8/25):

上の例では DoCollide メソッドが public になってしまっていますが、これは手抜きで、実際には非パブリックの方が好ましいでしょう。この辺をもうちょっとまじめに実装するには GetMethod を次のように呼び出します…


  …

  typeof(Space).GetMethod(

    "DoCollide",

    BindingFlags.NonPublic | BindingFlags.Static,

    null,

    new Type[]{a.GetType(), b.GetType()},

    null

   ).Invoke(null, new object[]{a, b});

  …


これで DoCollide を非パブリックなメソッドにすることができます。




新訂版 More Effective C++/スコット・メイヤーズ
¥3,990
Amazon.co.jp

Modern C++ Design―ジェネリック・プログラミングおよびデザイン・パターンを利用するための究極のテンプレート活用術 (C++ In‐Depth Series)/アンドレイ アレキサンドレスク
¥3,780
Amazon.co.jp