PHP Mentors — ダブルディスパッチ

1.5M ratings
277k ratings

See, that’s what the app is perfect for.

Sounds perfect Wahhhh, I don’t wanna

ダブルディスパッチ

オブジェクト指向プログラミングにおける実装パターンの1つにダブルディスパッチがあります。もともとはC++などメソッドのオーバーロード機構を持つプログラミング言語において、コンパイル時に決定できない型の情報を実行時に決定させるためのテクニックでした。後に、ケント・ベック氏の『実装パターン』にて、次のようにまとめなおされています。

1つの次元だけの変動性を表現するのであれば、選択メッセージで問題ない。「選択メッセージ」の例では、図形が描画されるメディアの種類という次元が1つだった。もし2つの独立した次元の変動性を表現する必要があるなら、2つの選択メッセージを順に呼び出す方法がある。
・・・(コード例)
ケント・ベック『実装パターン』 p.82 二重ディスパッチ

実際にどういう状況でダブルディスパッチが必要になるのか、また、どういった目的と根拠があるのかを関心や責務の分離の観点で考えてみます。

ソフトウェアの中で、次のように何らかのオブジェクトのメソッドを利用しているコードがあるとします。メソッドの中では、そのオブジェクトの別のメソッドを呼び出しています。

image

ここで、図の★の部分を独立した関心事として取り扱いたい場合を考えてみましょう。★の部分を別のオブジェクト(オブジェクトA)のメソッド(メソッドC)へ分離し、元のメソッド(オブジェクトBのメソッドA)から呼び出すようにします。

image

元々の★のコードからオブジェクトBのメソッドBを呼び出していました。分離した後でも同様に、オブジェクトAからオブジェクトBのメソッドを利用しており、依存がある状態です。依存といえばDI、特に今回は関心事として別々のカタマリのものですから、外から依存オブジェクトを注入する方針は基本となります。依存オブジェクトをインジェクトするにはいくつかの方法があります(コンストラクタインジェクションやセッターインジェクション)。最も局所的な方法は、依存オブジェクトが利用されるメソッドにおいて、引数として依存オブジェクトを渡す方法です。局所的というのは、オブジェクト全体で利用するというわけではなく、このメソッドでのみ利用するということです。

また、分離したオブジェクトAに対して、オブジェクトBは依存している状態になります。双方依存しあっているため、依存が循環している状態です(DIコンテナの実装によっては、依存が循環していると扱えない場合もあります)。クライアント側でオブジェクトAを生成し、メソッド呼び出し時に依存オブジェクトとして引数で渡すようにします。

この段階で、次のようになっています。

  • クライアント
    → オブジェクトBのメソッド呼び出し(オブジェクトAを引数で渡す)
  • オブジェクトB
    →(引数で渡された)オブジェクトAのメソッド呼び出し(自分自身を引数で渡す)

双方局所的な依存で結合されていますが、クライアントが生成するオブジェクトAをサブクラス等に置き換えることも可能です。この意味でオブジェクトBからオブジェクトAは動的(に選択可能)な依存であるとも言えます。

冒頭で取り上げた『実装パターン』で紹介されている例では、オブジェクトAの側とオブジェクトBの側、双方が可変であるようなケースを取り上げています。双方が可変であり、どの組み合わせで使われるのかが実行時にしか決まらない場合は、ダブルディスパッチを使う必然性があります。この場合実装としてはインターフェイス(または抽象クラス)を設け、各メソッドで受け取るのはインターフェイスであるようにします。

image

引数で直接オブジェクトを渡していた部分がインターフェイス経由になり、それぞれ実装ではなく抽象に依存するようになります。これにより、双方の可変性が確保されます。結果としては、SOLID原則の「依存関係逆転の原則(DIP)」を適用したと見ることができます。

まとめ

ダブルディスパッチを利用する経緯、とくにプログラミング言語の制約からではなく、2次元の可変性を実装するためのパターンという視点で見てみました。2つの軸に分かれそれぞれ可変性を持つ関心事がある場合、このようなパターンが使えるでしょう。ただし、『実装パターン』には以下の様な注意書きもあります。

二重ディスパッチは、いくつかの重複を生み、その分柔軟性が損なわれることになる。最初の選択メッセージを受信したクラスの型名が、2つ目の選択メッセージを受信するクラスのメソッドに散在してしまうのだ。・・・ある次元の方が他の次元よりも変化しやすいのであれば、そちらを2つ目のメッセージの受信者にしよう。
ケント・ベック『実装パターン』 p.83 二重ディスパッチ

関心を分離したことにより、ある面においては柔軟性が損なわれたように見える場合もあるということです。この点はケースバイケースでメリットとデメリットを測って導入していく必要があるでしょう。

参考

oop

See more posts like this on Tumblr

#oop