Javaにおけるダブルディスパッチパターン:多様な挙動の強化
別名
- マルチメソッド
ダブルディスパッチデザインパターンの目的
ダブルディスパッチパターンは、メソッド呼び出しに関与する2つのオブジェクトの型に基づいて動的なポリモーフィズムを実現するために使用されます。メソッド呼び出し元のオブジェクトと、パラメータとして渡されるオブジェクトの両方の実行時型に応じて、メソッドの動作を変えることができます。
実例を用いたダブルディスパッチパターンの詳細な説明
現実世界の例
物流会社では、トラック、ドローン、自転車などの様々な種類の配送車両が、壊れやすいもの、大型のもの、標準的なものなど、様々な種類の荷物とやり取りします。ダブルディスパッチデザインパターンは、最適な配送方法を決定するために使用されます。トラックは大型の荷物を、ドローンは軽量な荷物の迅速な配送を、自転車は市街地での配送を扱う場合があります。各車両と荷物の組み合わせは、車両と荷物の両方の型に基づいて実行時に動的に決定される、異なる処理と配送戦略をもたらします。
簡単に言うと
Javaのダブルディスパッチデザインパターンは、呼び出しに関与する2つのオブジェクトの型に基づいて実行する異なる関数をプログラムで選択できるようにし、それらの間の相互作用を処理する柔軟性を高めます。
Wikipediaによると
ソフトウェアエンジニアリングにおいて、ダブルディスパッチはマルチディスパッチの特殊な形式であり、呼び出しに関与する2つのオブジェクトの実行時型に応じて、関数呼び出しを異なる具体的な関数にディスパッチするメカニズムです。ほとんどのオブジェクト指向システムでは、コード内の関数呼び出しから呼び出される具体的な関数は、単一のオブジェクトの動的型に依存するため、シングルディスパッチ呼び出し、または単に仮想関数呼び出しとして知られています。
Javaにおけるダブルディスパッチパターンのプログラミング例
Javaのダブルディスパッチパターンは、異なるタイプのゲームオブジェクト間の衝突を処理するために使用されます。各ゲームオブジェクトは、GameObject
抽象クラスを拡張するクラスのインスタンスです。GameObject
クラスには、別のゲームオブジェクトとの衝突が発生した際の動作を定義するために各サブクラスでオーバーライドされるcollision(GameObject)
メソッドがあります。以下は、GameObject
クラスとそのサブクラスの簡略版です。
public abstract class GameObject {
// Other properties and methods...
public abstract void collision(GameObject gameObject);
}
public class FlamingAsteroid extends GameObject {
// Other properties and methods...
@Override
public void collision(GameObject gameObject) {
gameObject.collisionWithFlamingAsteroid(this);
}
}
public class SpaceStationMir extends GameObject {
// Other properties and methods...
@Override
public void collision(GameObject gameObject) {
gameObject.collisionWithSpaceStationMir(this);
}
}
Appクラスでは、ダブルディスパッチパターンを使用して、すべてのゲームオブジェクトのペア間の衝突をチェックします。
public static void main(String[] args) {
// initialize game objects and print their status
LOGGER.info("Init objects and print their status");
var objects = List.of(
new FlamingAsteroid(0, 0, 5, 5),
new SpaceStationMir(1, 1, 2, 2),
new Meteoroid(10, 10, 15, 15),
new SpaceStationIss(12, 12, 14, 14)
);
objects.forEach(o -> LOGGER.info(o.toString()));
// collision check
LOGGER.info("Collision check");
objects.forEach(o1 -> objects.forEach(o2 -> {
if (o1 != o2 && o1.intersectsWith(o2)) {
o1.collision(o2);
}
}));
// output eventual object statuses
LOGGER.info("Print object status after collision checks");
objects.forEach(o -> LOGGER.info(o.toString()));
}
2つのオブジェクト間の衝突が検出されると、第1のオブジェクト(o1)のcollision(GameObject)
メソッドが、第2のオブジェクト(o2)を引数として呼び出されます。このメソッド呼び出しは、実行時にo1のクラス内の適切なcollision(GameObject)
メソッドにディスパッチされます。このメソッド内で、別のメソッド呼び出しgameObject.collisionWithX(this)
がo2に対して行われます(ここでXはo1の型です)。これは実行時にo2のクラス内の適切なcollisionWithX(GameObject)
メソッドにディスパッチされます。これがJavaにおける「ダブルディスパッチ」です。2つのメソッド呼び出しが、2つのオブジェクトの型に基づいて実行時にディスパッチされます。
プログラムの出力は次のとおりです。
15:47:23.763 [main] INFO com.iluwatar.doubledispatch.App -- Init objects and print their status
15:47:23.772 [main] INFO com.iluwatar.doubledispatch.App -- FlamingAsteroid at [0,0,5,5] damaged=false onFire=true
15:47:23.772 [main] INFO com.iluwatar.doubledispatch.App -- SpaceStationMir at [1,1,2,2] damaged=false onFire=false
15:47:23.772 [main] INFO com.iluwatar.doubledispatch.App -- Meteoroid at [10,10,15,15] damaged=false onFire=false
15:47:23.772 [main] INFO com.iluwatar.doubledispatch.App -- SpaceStationIss at [12,12,14,14] damaged=false onFire=false
15:47:23.772 [main] INFO com.iluwatar.doubledispatch.App -- Collision check
15:47:23.772 [main] INFO com.iluwatar.doubledispatch.SpaceStationMir -- FlamingAsteroid hits SpaceStationMir. SpaceStationMir is damaged! SpaceStationMir is set on fire!
15:47:23.773 [main] INFO com.iluwatar.doubledispatch.Meteoroid -- SpaceStationMir hits FlamingAsteroid.
15:47:23.773 [main] INFO com.iluwatar.doubledispatch.SpaceStationMir -- {} is damaged! hits Meteoroid.
15:47:23.773 [main] INFO com.iluwatar.doubledispatch.Meteoroid -- SpaceStationIss hits Meteoroid.
15:47:23.773 [main] INFO com.iluwatar.doubledispatch.App -- Print object status after collision checks
15:47:23.773 [main] INFO com.iluwatar.doubledispatch.App -- FlamingAsteroid at [0,0,5,5] damaged=false onFire=true
15:47:23.773 [main] INFO com.iluwatar.doubledispatch.App -- SpaceStationMir at [1,1,2,2] damaged=true onFire=true
15:47:23.773 [main] INFO com.iluwatar.doubledispatch.App -- Meteoroid at [10,10,15,15] damaged=false onFire=false
15:47:23.773 [main] INFO com.iluwatar.doubledispatch.App -- SpaceStationIss at [12,12,14,14] damaged=true onFire=false
実例を用いたダブルディスパッチパターンの詳細な説明

Javaでダブルディスパッチパターンを使用する場面
- メソッドの動作を、呼び出されるオブジェクトだけでなく、引数の型に基づいて変更する必要がある場合。
- オブジェクトの型に対するif-elseまたはswitch-case型のチェックが煩雑で、スケーラブルでないシナリオ。
- 他のドメインクラスに関する複雑な意思決定ロジックでコードを汚染することなく、ドメインクラスで操作を実装する場合。
Javaにおけるダブルディスパッチパターンの現実世界の応用事例
- 異なるタイプのマウスイベントが異なるタイプの要素と対話することに基づいて異なるアクションが取られるグラフィカルユーザーインターフェース。
- 異なるタイプのオブジェクト間の相互作用が異なる動作をトリガーする必要があるシミュレーションシステム。
ダブルディスパッチパターンのメリットとデメリット
メリット
- オブジェクト間の相互作用を理解しやすく、保守しやすい方法で処理することにより、コードの柔軟性を高めます。
- 既存のクラスを変更せずに新しいクラスを導入できるため、オープン/クローズド原則への準拠に役立ちます。
デメリット
- 特にこのパターンをネイティブにサポートしていないJavaなどの言語では、より複雑なコード構造につながる可能性があります。
- 新しいクラスが追加されると、保守と拡張にさらに労力が必要になる場合があります。
関連するJavaデザインパターン
- ストラテジー:実行時にアルゴリズムを選択するために使用される点で目的は似ていますが、ストラテジーは複数のオブジェクト間の相互作用ではなく、単一のオブジェクトコンテキストに焦点を当てています。
- ビジター:一連の要素オブジェクトに対して実行される操作をカプセル化するために、ダブルディスパッチと共に使用されることがよくあります。