ソフトウェア エンジニアリングにおいて、二重ディスパッチは多重ディスパッチの特殊な形式であり、呼び出しに関係する 2 つのオブジェクトの実行時型に応じて、関数呼び出しを異なる具体的な関数にディスパッチするメカニズムです。ほとんどのオブジェクト指向システムでは、コード内の関数呼び出しから呼び出される具体的な関数は、単一のオブジェクトの動的型に依存するため、単一ディスパッチ呼び出し、または単に仮想関数呼び出しと呼ばれます。
ダン・インガルスは、Smalltalkで二重ディスパッチを使用する方法を初めて説明し、それを多重ポリモーフィズムと呼びました。[1]
概要
対処される一般的な問題は、受信者だけでなく引数に応じて、メッセージをさまざまなメソッドに送信する方法です。
そのために、CLOSのようなシステムは多重ディスパッチを実装します。二重ディスパッチは、多重ディスパッチをサポートしていないシステムでポリモーフィズムを徐々に減らすもう 1 つのソリューションです。
ユースケース
ダブルディスパッチは、計算の選択が引数のランタイム型に依存する状況で役立ちます。たとえば、プログラマーは次のような状況でダブルディスパッチを使用できます。
- 混合オブジェクト セットのソート:アルゴリズムでは、オブジェクトのリストを何らかの標準順序でソートする必要があります。ある要素が別の要素の前に来るかどうかを決定するには、両方のタイプと、場合によってはフィールドのサブセットに関する知識が必要です。
- 適応衝突アルゴリズムでは通常、異なるオブジェクト間の衝突を異なる方法で処理する必要があります。典型的な例としては、宇宙船と小惑星の衝突が宇宙船と宇宙ステーションの衝突とは異なる方法で計算されるゲーム環境が挙げられます。[2]
- 重なり合うスプライトの交差点を異なる方法でレンダリングする必要があるペイント アルゴリズム。
- 人事管理システムは、異なるタイプの仕事を異なる人員に割り当て
scheduleます。会計担当者としてタイプされた人オブジェクトとエンジニアリングとしてタイプされた仕事オブジェクトが与えられたアルゴリズムは、その人をその仕事にスケジュールすることを拒否します。 - 正しいイベント処理ルーチンを呼び出すために、イベント タイプと受信オブジェクトのタイプの両方を使用するイベント処理システム。
- ロックとキーのシステムでは、多くの種類のロックと多くの種類のキーがあり、すべての種類のキーで複数の種類のロックを開けることができます。関係するオブジェクトの種類を知る必要があるだけでなく、「特定のキーが特定のロックを開けるかどうかを確認するのに関連する特定のキーに関する情報」のサブセットは、ロックの種類によって異なります。
よく使われる慣用句
上で示した例のような一般的な慣用句では、実行時の呼び出しの引数の型に基づいて適切なアルゴリズムが選択されます。したがって、呼び出しは、呼び出しの動的な解決に関連する通常の追加のパフォーマンス コストの影響を受け、通常は単一のメソッド ディスパッチのみをサポートする言語よりもコストが高くなります。たとえば、C++では、動的な関数呼び出しは通常、単一の オフセット計算によって解決されます。これは、コンパイラがオブジェクトのメソッド テーブル内の関数の場所を認識しており、オフセットを静的に計算できるため可能です。二重ディスパッチをサポートする言語では、コンパイラが実行時にメソッド テーブル内のメソッドのオフセットを計算するコードを生成する必要があり、それによって全体的な命令パスの長さが増加するため、コストがわずかに高くなります (その量は関数の呼び出しの合計数以下になる可能性が高いため、それほど重要ではありません)。
Rubyの例
一般的な使用例は、画面やプリンター、あるいはまだ存在していない他の何かであるディスプレイ ポートにオブジェクトを表示することです。これは、これらのさまざまなメディアを処理する方法の単純な実装です。
class Rectangle def display_on ( port ) # オブジェクトクラスに基づいて適切なコードを選択しますcase port when DisplayPort # DisplayPort に表示するためのコードwhen PrinterPort # PrinterPort に表示するためのコードwhen RemotePort # RemotePort に表示するためのコードend end end
Oval、Triangle、およびメディア上に表示したいその他のオブジェクトについても、同じことを記述する必要があり、新しいタイプのポートを作成する場合はすべてを書き直す必要があります。問題は、複数のレベルのポリモーフィズムが存在することです。1 つは、display_on メソッドをオブジェクトにディスパッチするためのものであり、もう 1 つは、表示用の適切なコード (またはメソッド) を選択するためのものです。
よりクリーンで保守しやすい解決策は、2 回目のディスパッチを実行して、今度はメディア上にオブジェクトを表示するための適切な方法を選択することです。
class Rectangle def display_on ( port ) # 2番目のディスパッチポート. display_rectangle ( self ) end end
class Oval def display_on ( port ) # 2番目のディスパッチポート. display_oval ( self ) end end
class DisplayPort def display_rectangle ( object ) # DisplayPort に長方形を表示するためのコードend def display_oval ( object ) # DisplayPort に楕円を表示するためのコードend # ... end
class PrinterPort def display_rectangle ( object ) # PrinterPort に長方形を表示するためのコードend def display_oval ( object ) # PrinterPort に楕円を表示するためのコードend # ... end
C++ でのダブルディスパッチ
一見すると、二重ディスパッチは関数オーバーロードの自然な結果のように見えます。関数オーバーロードにより、呼び出される関数は引数の型に依存できるようになります。ただし、関数オーバーロードは、関数の内部名が引数の型をエンコードする「名前マングリング」を使用してコンパイル時に行われます。たとえば、関数は内部的に__foo_ifoo(int)と呼ばれ、関数は__foo_dと呼ばれる場合があります。したがって、名前の衝突はなく、仮想テーブルの検索もありません。対照的に、動的ディスパッチは呼び出しオブジェクトの型に基づいています。つまり、関数オーバーロードの代わりに仮想関数(オーバーライド) を使用し、vtable の検索が発生します。C ++で記述された、ゲームでの衝突に関する次の例を考えてみましょう。
foo(double)
クラスSpaceShip {};クラスApolloSpacecraft : public SpaceShip {};
class Asteroid { public : virtual void CollideWith ( SpaceShip & ) { std :: cout << "Asteroid hit a SpaceShip \n " ; } virtual void CollideWith ( ApolloSpacecraft & ) { std :: cout << "Asteroid hit an ApolloSpacecraft \n " ; } };
クラスExplodingAsteroid : public Asteroid { public : void CollideWith ( SpaceShip & ) override { std :: cout << "ExplodingAsteroid が SpaceShip に衝突しました\n " ; } void CollideWith ( ApolloSpacecraft & ) override { std :: cout << "ExplodingAsteroid が ApolloSpacecraft に衝突しました\n " ; } };
以下の条件に該当する場合:
小惑星theAsteroid ;宇宙船theSpaceShip ;アポロ宇宙船theApolloSpacecraft ;
そして、関数のオーバーロードにより、
小惑星。衝突(宇宙船);小惑星。衝突(アポロ宇宙船);
は、動的ディスパッチを使用せずに、
それぞれ、Asteroid hit a SpaceShipおよびを出力します。さらに、Asteroid hit an ApolloSpacecraft
ExplodingAsteroid theExplodingAsteroid ; theExplodingAsteroid.CollideWith ( theSpaceShip ) ; theExplodingAsteroid.CollideWith ( theApolloSpacecraft ) ;
それぞれ、動的ディスパッチなしで、およびが
ExplodingAsteroid hit a SpaceShip印刷されます。ExplodingAsteroid hit an ApolloSpacecraft
への参照ではAsteroid動的ディスパッチが使用され、次のコードになります。
小惑星& theAsteroidReference = theExplodingAsteroid ;小惑星参照。衝突(宇宙船);小惑星参照。衝突(アポロ宇宙船);
ExplodingAsteroid hit a SpaceShipと を再び予想どおりに出力しますExplodingAsteroid hit an ApolloSpacecraft。ただし、次のコードは期待どおりに動作しません。
SpaceShip & theSpaceShipReference = theApolloSpacecraft ; theAsteroid.CollideWith ( theSpaceShipReference ) ; theAsteroidReference.CollideWith ( theSpaceShipReference ) ;
望ましい動作は、これらの呼び出しを、その引数として受け取る関数にバインドすることです。theApolloSpacecraftこれは、その変数のインスタンス化された型であるため、期待される出力は および になりますAsteroid hit an ApolloSpacecraft。ExplodingAsteroid hit an ApolloSpacecraftただし、実際の出力はAsteroid hit a SpaceShipおよび ですExplodingAsteroid hit a SpaceShip。問題は、仮想関数は C++ で動的にディスパッチされるのに対し、関数のオーバーロードは静的に行われることです。
上記の問題は、ビジターパターンなどを使用して二重ディスパッチをシミュレートすることで解決できます。既存のコードを拡張して、との両方に関数が与えられる
とします。SpaceShipApolloSpacecraft
仮想void CollideWith ( Asteroid & inAsteroid ) { inAsteroid . CollideWith ( * this ); }
次に、前の例はまだ正しく動作しませんが、宇宙船がエージェントになるように呼び出しを再構成すると、目的の動作が得られます。
SpaceShip & theSpaceShipReference = theApolloSpacecraft ; Asteroid & theAsteroidReference = theExplodingAsteroid ; theSpaceShipReference.CollideWith ( theAsteroid ) ; theSpaceShipReference.CollideWith ( theAsteroidReference ) ;
Asteroid hit an ApolloSpacecraft予想どおり、 とが出力されますExplodingAsteroid hit an ApolloSpacecraft。重要なのは、 がtheSpaceShipReference.CollideWith(theAsteroidReference);実行時に次の処理を実行することです。
theSpaceShipReferenceは参照なので、C++ は vtable で正しいメソッドを検索します。この場合、 を呼び出しますApolloSpacecraft::CollideWith(Asteroid&)。- 内では
ApolloSpacecraft::CollideWith(Asteroid&)、inAsteroidは参照なので、別の vtable 検索inAsteroid.CollideWith(*this)が実行されます。この場合、はへの参照なので、が呼び出されます。inAsteroidExplodingAsteroidExplodingAsteroid::CollideWith(ApolloSpacecraft&)
C# でのダブルディスパッチ
C#では、引数を受け入れるインスタンスメソッドを呼び出すときに、ビジターパターンを使用せずに多重ディスパッチを実現できます。これは、従来のポリモーフィズムを使用しながら、引数を動的にキャストすることで実現されます。[3]ランタイムバインダーは、実行時に適切なメソッドオーバーロードを選択します。この決定では、オブジェクトインスタンスの実行時型(ポリモーフィズム)と引数の実行時型が考慮されます。
エッフェルでの二重派遣
Eiffelプログラミング言語は、エージェントの概念をダブルディスパッチ問題に適用できます。以下の例では、エージェント言語構造をダブルディスパッチ問題に適用しています。
さまざまな形式の SHAPE と、その上に SHAPE を描画する SURFACE の描画に関する問題領域について考えてみましょう。SHAPE と SURFACE はどちらも、自分自身では「draw」という関数を認識していますが、お互いには認識していません。ビジター パターンを使用して、2 つのタイプのオブジェクトがダブル ディスパッチで共変的に相互作用するようにします。
課題は、多形性 SURFACE に、それ自身の上に多形性 SHAPE を描画させることです。
出力
以下の出力例は、2 つの SURFACE ビジター オブジェクトがポリモーフィックな SHAPE オブジェクトのリストにポリモーフィックに渡された結果を示しています。ビジター コード パターンは、SHAPE と SURFACE を一般的に認識しているだけで、特定の型は認識していません。代わりに、コードは実行時のポリモーフィズムとエージェントのメカニズムに依存して、これら 2 つの遅延クラスとその子孫の間に非常に柔軟な共変関係を実現します。
ETCHASKETCHに赤いポリゴンを描くGRAFFITI_WALLに赤いPOLYGONを 描くETCHASKETCHに灰色の長方形 を描くGRAFFITI_WALLに灰色のRECTANGLE を描くETCHASKETCHで緑の四辺形 を描きますGRAFFITI_WALLに緑のQUADRILATELAL を描くETCHASKETCHに青い平行四辺形 を描きますGRAFFITI_WALLに青いPARALLELOGRAM を描くETCHASKETCHに黄色のポリゴン を描くGRAFFITI_WALLに黄色のPOLYGON を描くETCHASKETCHで紫色の長方形 を描きますGRAFFITI_WALLに 紫色のRECTANGLE を描く
設定
SHAPE または SURFACE を見る前に、ダブルディスパッチの高レベルの分離された使用法を調べる必要があります。
訪問者パターン
ビジター パターンは、ビジター オブジェクトがデータ構造 (リスト、ツリーなど) の要素を多態的に訪問し、訪問したターゲット構造内の多態的要素オブジェクトに対して何らかのアクション (呼び出しまたはエージェント) を適用することによって機能します。
以下の例では、多態的な SHAPE オブジェクトのリストを作成し、多態的な SURFACE を使用して各オブジェクトにアクセスし、SHAPE を SURFACE 上に描画するように要求します。
作る
-- 表面に形状を印刷します。
地元
l_shapes :配列リスト[シェイプ]
l_surfaces : ARRAYED_LIST [サーフェス]
する
l_shapesを作成します。make ( 6 )
l_shapes.extend ( { POLYGON }を作成します。make_with_color ( " red " ))
l_shapes.extend ( create { RECTANGLE } .make_with_color ( "grey" ))を作成します。
l_shapes . extend ( { QUADRILATERAL }を作成します。make_with_color ( "green" ))
l_shapes . extend ( create { PARALLELOGRAM }. make_with_color ( "blue" ))
l_shapes.extend ( { POLYGON }を作成します。make_with_color ( " yellow " ))
l_shapes.extend ( create { RECTANGLE } .make_with_color ( "purple" ))を作成します。
l_surfacesを作成します。make ( 2 )
l_surfaces.extend ( { ETCHASKETCH } .makeを作成)
l_surfaces.extend ( { GRAFFITI_WALL } .makeを作成)
l_shapes をic_shapesループとして横断する
l_surfaces をic_surfacesループとして横断する
ic_surfaces.item.drawing_agent ( ic_shapes.item.drawing_data_agent )は、
終わり
終わり
終わり
まず、SHAPE オブジェクトと SURFACE オブジェクトのコレクションを作成します。次に、リストの 1 つ (SHAPE) を反復処理して、もう 1 つのリスト (SURFACE) の要素が各リストを順番に参照できるようにします。上記のコード例では、SURFACE オブジェクトが SHAPE オブジェクトを参照しています。
このコードは、ダブルディスパッチパターンの最初の呼び出し (ディスパッチ) である `drawing_agent' を介して間接的に {SURFACE}.draw に多態的な呼び出しを行います。間接的で多態的なエージェント (`drawing_data_agent') を渡すことで、ビジターコードは次の 2 つのことだけを認識できるようになります。
- サーフェスの描画エージェントは何ですか (例: 行番号 21 の al_surface.drawing_agent)?
- 図形の描画データ エージェントとは何ですか (例: 行番号 21 の al_shape.drawing_data_agent)?
SURFACE と SHAPE はどちらも独自のエージェントを定義するため、訪問者コードは、ポリモーフィックであろうとなかろうと、適切な呼び出しが何であるかを知る必要がありません。このレベルの間接化と分離は、何らかの形式のリフレクションまたはシグネチャ マッチングによる機能オーバーロードを経由しない限り、C、C++、Java などの他の一般的な言語では実現できません。
表面
{SURFACE}.draw へのポリモーフィック呼び出し内にはエージェントへの呼び出しがあり、これがダブルディスパッチ パターンの 2 番目のポリモーフィック呼び出しまたはディスパッチになります。
延期クラス
表面
機能{ NONE } -- 初期化
作る
-- 現在のものを初期化します。
する
drawing_agent :=エージェント描画
終わり
機能-- アクセス
drawing_agent : PROCEDURE [ ANY , TUPLE [ STRING , STRING ]]
-- Current の描画エージェント。
機能{なし} -- 実装
描画( a_data_agent : FUNCTION [ ANY , TUPLE , TUPLE [名前,色:文字列]] )
-- Current に `a_shape' を描画します。
地元
l_result :タプル[名前,色:文字列]
する
l_result := a_data_agent (無効)
print ( "draw a " + l_result . color + " " + l_result . name + " on " + type + "%N" )
終わり
タイプ: STRING
-- 現在のタイプ名。
延期終了
終わり
行番号 19 のエージェント引数と行番号 24 の呼び出しは、どちらもポリモーフィックかつ分離されています。エージェントが分離されているのは、{SURFACE}.draw 機能では `a_data_agent' がどのクラスに基づいているかがわからないためです。操作エージェントがどのクラスから派生したかを知る方法はないため、SHAPE またはその子孫のいずれかから派生する必要はありません。これは、他の言語の単一継承、動的、ポリモーフィック バインディングに対する Eiffel エージェントの明確な利点です。
エージェントは実行時に動的に多態的になります。これは、オブジェクトは必要なときに動的に作成され、オブジェクト化されたルーチンのバージョンはその時点で決定されるためです。強く結び付けられた知識は、エージェント シグネチャの Result 型、つまり 2 つの要素を持つ名前付き TUPLE のみです。ただし、この特定の要件は、囲む機能の要求に基づいています (たとえば、行番号 25 では、SURFACE の「描画」機能を実行するために TUPLE の名前付き要素を使用しています)。これは必要であり、回避されていません (回避できない可能性もあります)。
最後に、`drawing_agent' 機能のみが任意のクライアントにエクスポートされることに注意してください。これは、ビジター パターン コード (このクラスの唯一のクライアント) がジョブを実行するためにエージェントについてのみ知っておく必要があることを意味します (たとえば、訪問したオブジェクトに適用される機能としてエージェントを使用するなど)。
形
SHAPE クラスには、おそらく SURFACE 上に描画されるものの基礎 (描画データなど) がありますが、必ずしもそうである必要はありません。エージェントは、SHAPE との共変関係を可能な限り分離するために必要な間接性とクラスに依存しない機能を提供します。
さらに、SHAPE は、クライアントに完全にエクスポートされた機能として `drawing_data_agent' のみを提供することに注意してください。したがって、作成以外で SHAPE と対話する唯一の方法は、`drawing_data_agent' の機能を使用することです。これは、あらゆるクライアントによって間接的かつ多態的に SHAPE の描画データを収集するために使用されます。
延期クラス
形
機能{ NONE } -- 初期化
make_with_color ( a_color :似た色)
-- `a_color' を `color' として作成します。
する
色:= a_color
drawing_data_agent :=エージェントdrawing_data
確保する
color_set :色.同じ文字列( a_color )
終わり
機能-- アクセス
drawing_data_agent : FUNCTION [ ANY 、TUPLE 、drawing_dataと同様]
-- 描画用のデータエージェント。
機能{なし} -- 実装
drawing_data : TUPLE [ name :名前と同じ; color :色と同じ]
-- Current の描画に必要なデータ。
する
結果:= [名前、色]
終わり
名前:文字列
-- 現在のオブジェクト名。
延期終了
色:文字列
-- 電流の色。
終わり
クラシック宇宙船の例
典型的な宇宙船の例のバリエーションとして、1 つ以上の宇宙船オブジェクトが、放浪小惑星や宇宙ステーションなどの他のアイテムで満たされた宇宙を歩き回っています。私たちが求めているのは、架空の宇宙における 2 つの共変オブジェクト間の遭遇 (衝突の可能性など) を処理するための二重ディスパッチ メソッドです。以下の例では、USS エンタープライズと USS エクセルシオールの出力エクスカーションは次のようになります。
エンタープライズ号の位置が A-001 から A-002 に変更されます。
エンタープライズ号は回避行動を取り、小惑星「ローグ 1」を回避しました。
エンタープライズ号の位置が A-002 から A-003 に変更されます。
エンタープライズ号は回避行動を取り、小惑星「ローグ 2」を回避しました。
スターシップ・エンタープライズが通過する際に、科学チームをスターシップ・エクセルシオールに送信します。
エンタープライズ号の位置が A-003 から A-004 に変更されます。
スターシップ エクセルシオの位置が A-003 から A-005 に変更されます。
エンタープライズ号は回避行動を取り、小惑星「ローグ 3」を回避しました。
スターシップ エクセルシオールは宇宙ステーション ディープ スペース 9 の近くにあり、ドッキング可能です。
エンタープライズ号の位置が A-004 から A-005 に変更されます。
スターシップ・エンタープライズが通過する際に、科学チームをスターシップ・エクセルシオールに送信します。
スターシップ・エンタープライズは宇宙ステーション・ディープ・スペース9の近くにあり、ドッキング可能です。
ビジター
古典的な Spaceship の例のビジターにも、二重ディスパッチ メカニズムがあります。
作る
-- 宇宙船オブジェクトが宇宙を訪れ、宇宙内を移動できるようにします。
地元
l_universe : ARRAYED_LIST [ SPACE_OBJECT ]
l_enterprise 、
l_excelsior :宇宙船
する
l_enterpriseを作成します。make_with_name ( " Enterprise" 、"A-001" )
l_excelsiorを作成します。make_with_name ( " Excelsior" 、"A-003" )
l_universeを作成します。make ( 0 )
l_universe 。強制( l_enterprise )
l_universe . force ( { ASTEROID }を作成します。make_with_name ( "Rogue 1" 、"A-002" ))
l_universe . force ( { ASTEROID }を作成します。make_with_name ( "Rogue 2" 、"A-003" ))
l_universe 。力( l_excelsior )
l_universe . force ( { ASTEROID }を作成します。make_with_name ( "Rogue 3" 、"A-004" ))
l_universe . force ( create { SPACESTATION }. make_with_name ( "ディープ・スペース9" , "A-005" ))
訪問( l_enterprise 、l_universe )
l_enterprise . set_position ( "A-002" )
訪問( l_enterprise 、l_universe )
l_enterprise . set_position ( "A-003" )
訪問( l_enterprise 、l_universe )
l_enterprise . set_position ( "A-004" )
l_excelsior.set_position ( "A - 005" )を設定します。
訪問( l_enterprise 、l_universe )
訪問( l_excelsior 、l_universe )
l_enterprise . set_position ( "A-005" )
訪問( l_enterprise 、l_universe )
終わり
機能{なし} -- 実装
visit ( a_object : SPACE_OBJECT ; a_universe : ARRAYED_LIST [ SPACE_OBJECT ] )
-- `a_object' は `a_universe' を訪問します。
する
a_universeをic_universeループとして横断する
添付の{ SPACE_OBJECT } ic_universe . item をal_universe_objectとしてチェックし、
a_object . meet_agent . call ( [ al_universe_object . sensors_data_agent ] ) は、
終わり
終わり
終わり
ダブルディスパッチは行番号 35 で確認できます。ここでは、2 つの間接エージェントが連携して、互いに完全にポリモーフィックに連携して動作する 2 つの共変呼び出しを提供しています。`visit' 機能の `a_object' には `encounter_agent' があり、これは `al_universe_object' から取得される `sensor_data_agent' のセンサーデータで呼び出されます。この特定の例のもう 1 つの興味深い部分は、SPACE_OBJECT クラスとその `encounter' 機能です。
訪問者の行動
SPACE_OBJECT のエクスポートされる機能は、エンカウンターおよびセンサー データのエージェントと、新しい位置を設定する機能のみです。1 つのオブジェクト (宇宙船) が宇宙の各オブジェクトを訪問すると、センサー データが収集され、そのエンカウンター エージェント内の訪問オブジェクトに渡されます。そこで、sensor_data_agent からのセンサー データ (つまり、sensor_data_agent クエリによって返される sensors_data TUPLE のデータ要素項目) が現在のオブジェクトに対して評価され、その評価に基づいて一連のアクションが実行されます (以下の SPACE_OBJECT の「encounter」を参照)。その他のすべてのデータは {NONE} にエクスポートされます。これは、C、C++、および Java の Private スコープに似ています。エクスポートされない機能として、データとルーチンは各 SPACE_OBJECT によって内部的にのみ使用されます。最後に、encounter の「print」呼び出しには、SPACE_OBJECT の可能な子孫クラスに関する特定の情報は含まれないことに注意してください。このレベルの継承で見つかるのは、一般的な SPACE_OBJECT の属性とルーチンからわかることに完全に基づいた一般的な関係の側面だけです。宇宙船、宇宙ステーション、小惑星について私たちが知っていることや想像していることに基づいて、`print' の出力が人間にとって意味をなすという事実は、単に論理的な計画または偶然です。SPACE_OBJECT は、その子孫に関する特定の知識に基づいてプログラムされていません。
延期クラス
スペースオブジェクト
機能{ NONE } -- 初期化
make_with_name ( a_name :名前と同じ; a_position :位置と同じ)
-- `a_name' と `a_position' で Current を初期化します。
する
名前:= a_name
位置:= a_position
センサーデータエージェント:=エージェントセンサーデータ
遭遇エージェント:=エージェント遭遇
確保する
name_set :名前.同じ文字列( a_name )
位置セット:位置.同じ文字列( a_position )
終わり
機能-- アクセス
遭遇エージェント:手続き[任意,タプル]
-- Current との遭遇を管理するエージェント。
Sensor_data_agent : FUNCTION [ ANY 、TUPLE 、Sensor_data_anchorと同様に添付]
-- 現在のセンサー データを返すエージェント。
機能-- 設定
set_position ( a_position :同様の位置)
-- `a_position' で `position' を設定します。
する
print ( type + " " + name + " は位置を " + position + " から " + a_position + ".%N"に変更します)
位置:= a_position
確保する
位置セット:位置.同じ文字列( a_position )
終わり
機能{なし} -- 実装
遭遇( a_sensor_agent : FUNCTION [ ANY 、TUPLE 、sensors_data_anchorのように添付] )
-- `a_radar_agent' を使用して Current の衝突状態を検出し、報告します。
する
a_sensor_agent . call ( [ Void ] )
添付の{ sensor_data_anchor } a_sensor_agent.last_resultをal_sensor_dataとしてチェックし、
name.same_string ( al_sensor_data.name )でない場合は
if (位置.same_string ( al_sensor_data.position ) ) then
if (( al_sensor_data . is_dockableかつis_dockable )かつ
( is_mannedおよびal_sensor_data . is_manned )および
( is_manueverableとal_sensor_data 。is_not_manueverable )) then
印刷( type + " " + name + " は " + al_sensor_dataの近くにあります。type + " " +
al_sensor_data . name + " であり、ドッキング可能です。%N" )
elseif (( is_dockableかつal_sensor_data . is_dockable )かつ
( is_mannedおよびal_sensor_data . is_manned )および
( is_manueverableとal_sensor_data . is_manueverable )) then
print ( type + " " + name + " は科学チームを " + al_sensor_dataに送信します。type + " " +
al_sensor_data . name + " 通過するとき!%N" )
elseif ( is_mannedかつal_sensor_data . is_not_manned ) then
print ( type + " " + name + " は回避行動を取り、 " +を回避します
al_sensor_data . type + " `" + al_sensor_data . name + "'!%N" )
終わり
終わり
終わり
終わり
終わり
名前:文字列
-- 現在の名前。
タイプ: STRING
-- 電流の種類。
延期
終わり
位置:文字列
-- 電流の位置。
ドッキング可能かどうか:ブール値
-- Current は他の有人オブジェクトとドッキングできますか?
延期
終わり
is_manned :ブール値
-- Current は有人オブジェクトですか?
延期
終わり
操作可能:ブール値
-- Current は移動可能ですか?
延期
終わり
Sensor_data : Sensor_data_anchorのように添付
-- 現在のセンサーデータ。
する
結果:= [名前、タイプ、位置、ドッキング可能、ドッキング可能ではない、有人、有人ではない、操作可能、操作可能ではない]
終わり
センサーデータアンカー:取り外し可能タプル[名前、タイプ、位置:文字列;ドッキング可能、ドッキング不可、有人、有人不可、操作可能、操作不可:ブール値]
-- 現在のセンサー データ タイプ アンカー。
終わり
SPACE_OBJECT には 3 つの子孫クラスがあります。
SPACE_OBJECT
小惑星
宇宙船 宇宙
ステーション
この例では、ASTEROID クラスは「Rogue」アイテムに、SPACESHIP は 2 つの宇宙船に、SPACESTATION は Deep Space Nine に使用されます。各クラスで、唯一の特殊化は、オブジェクトの「type」機能と特定のプロパティの設定です。「name」は、「position」と同様に作成ルーチンで提供されます。たとえば、以下は SPACESHIP の例です。
クラス
宇宙船
継承する
スペースオブジェクト
作成する
名前で作る
機能{なし} -- 実装
タイプ: STRING = "Starship"
-- <先駆者>
is_dockable :ブール値= True
-- <先駆者>
is_manned :ブール値= True
-- <先駆者>
is_manueverable : BOOLEAN = True
-- <先駆者>
終わり
したがって、私たちの宇宙のあらゆる宇宙船は、ドッキング可能で、有人であり、操縦可能です。小惑星などの他のオブジェクトは、これらのいずれにも該当しません。一方、宇宙ステーションは、ドッキング可能で有人ですが、操縦できません。したがって、あるオブジェクトが別のオブジェクトと遭遇すると、まず位置が互いに近いかどうかを確認し、近い場合は、オブジェクトは基本プロパティに基づいて相互作用します。同じタイプと名前のオブジェクトは同じオブジェクトと見なされるため、相互作用は論理的に許可されないことに注意してください。
エッフェルの例の結論
ダブルディスパッチに関しては、Eiffel を使用すると、デザイナーとプログラマーは、クラスルーチンをエージェントにして、直接オブジェクト機能の呼び出しを行う代わりにそれらのエージェントを渡すことによって、クラスからクラスルーチンを切り離すことで、オブジェクト間の直接的な知識のレベルをさらに削減できます。エージェントには特定のシグネチャと可能な結果 (クエリの場合) もあるため、特定のオブジェクトの詳細を放棄することなく、理想的な静的型チェック手段になります。エージェントは完全にポリモーフィックであるため、結果のコードには、ローカルジョブを実行するために必要な特定の知識のみが含まれます。それ以外の場合は、特定の内部クラス機能の知識が多くの共変オブジェクトに分散されることで、メンテナンスの負担が追加されることはありません。エージェントの使用とメカニズムにより、これが保証されます。エージェントの使用の考えられる欠点の 1 つは、エージェントが直接呼び出しの対応物よりも計算コストが高いことです。これを念頭に置いて、ダブルディスパッチでのエージェントの使用と、ビジターパターンでのエージェントの適用を決して想定しないでください。共変相互作用に関係するクラス タイプのドメインに関する設計上の制限が明確にわかる場合、直接呼び出しは計算コストの点から見てより効率的なソリューションです。ただし、関与するタイプのクラス ドメインが拡大または大幅に変化することが予想される場合、エージェントはダブル ディスパッチ パターンでのメンテナンスの負担を軽減する優れたソリューションとなります。
参照
参考文献
- ^多重ポリモーフィズムを処理する ためのシンプルな手法。OOPSLA '86 議事録、オブジェクト指向プログラミング システム、言語、アプリケーション、347 ~ 349 ページ、1986 年 11 月。SIGPLAN Notices、21(11) として印刷。ISBN 0-89791-204-7
- ^ More Effective C++ (Scott Meyers 著、Addison-Wesley、1996 年)
- ^ 「dynamic 型の使用 (C# プログラミング ガイド)」。Microsoft Developer Network。Microsoft。2009年 9 月 30 日。2016年5 月 25 日閲覧。...
メソッド呼び出しの 1 つ以上の引数が dynamic 型である場合、オーバーロード解決はコンパイル時ではなく実行時に行われます。
