オブジェクト指向プログラミングにおいて、転送とは、オブジェクトのメンバー(プロパティまたはメソッド)を使用すると、実際には別のオブジェクトの対応するメンバーが使用されることを意味します。つまり、使用は別のオブジェクトに転送されます。転送は、一部のメンバーが別のオブジェクトに転送され、他のメンバーは直接使用されるオブジェクトによって処理される、さまざまな設計パターンで使用されます。転送オブジェクトは、多くの場合、ラッパー オブジェクトと呼ばれ、明示的な転送メンバーはラッパー関数と呼ばれます。
委任
転送は委任とよく混同されますが、正式にはこれらは相補的な概念です。どちらの場合も 2 つのオブジェクトがあり、最初の (送信側、ラッパー) オブジェクトは 2 番目の (受信側、ラッパー) オブジェクトを使用して、たとえばメソッドを呼び出します。これらは、self受信側オブジェクト (正式には、受信側オブジェクトのメソッドの評価環境) で参照されるものが異なります。委任では送信側オブジェクトを参照しますが、転送では受信側オブジェクトを参照します。は、動的ディスパッチself(メソッド解決: メソッド名がどの関数を参照するか)の一部として暗黙的に使用されることが多いことに注意してください。
転送と委任の違いは、ラッパーを介して呼び出されたときに、ラッパー内の自己パラメータがバインドされるかどうかです。委任では、自己パラメータはラッパーにバインドされ、転送ではラッパーにバインドされます。... 転送は自動メッセージ再送信の形式です。委任は、通常の継承のようにコンパイル/リンク時にではなく、実行時に親 (スーパークラス) をバインドする継承の形式です。[1]
たとえば、次のコードがあるとします。
// 送信者
void n () { print ( "n1" ); }
// 受信機
void m () { print ( "m2, " ); n (); }
void n () { print ( "n2" ); }
委任により、m()出力されますメートル2、メートル1n()は元の(送信)オブジェクトのコンテキストで評価されるが、転送の場合は次のように出力される。平方メートル、平方メートルはn()受信オブジェクトのコンテキストで評価される。[1]
普段の使用では、転送は「委任」と呼ばれるか、委任の一形態と見なされることが多いが、注意深い使用では、参照するものによって明確に区別される。委任は継承selfに類似しており、評価コンテキストを変更せずに動作の再利用(具体的にはコードの再利用)を可能にするが、転送は、実行が(元の)送信オブジェクトではなく受信(メンバー)オブジェクトのみに依存するため、構成に類似している。どちらの場合も、再利用は動的であり、実行時に決定される(使用が委任または転送されるオブジェクトに基づく)のに対し、静的であり、コンパイル/リンク時に決定される(継承元のクラスに基づく)のではない。継承と同様に、委任では送信オブジェクトが元の動作を変更できるが、脆弱な基本クラスに類似した問題の影響を受けやすい。一方、転送ではより強力なカプセル化が提供され、これらの問題を回避できる。継承よりも構成を参照。[1]
例
Java での明示的な転送の簡単な例: のインスタンスがそのフィールドのメソッドBへの呼び出しを転送します。
fooa
クラス B { A a ; T foo () { return a . foo (); } }
を実行する場合a.foo()、thisオブジェクトはa( のサブタイプA) であり、元のオブジェクト ( のインスタンスB) ではないことに注意してください。さらに、aは のインスタンスである必要はなくA、サブタイプのインスタンスである場合もあります。実際、 はAクラスである必要はなく、インターフェース/プロトコルである場合もあります。
継承とは対照的に、 では がfooスーパークラスA(インターフェースではなくクラスである必要があります) で定義され、 がサブクラスのインスタンスで呼び出されるとB、 で定義されたコードが使用されますがA、thisオブジェクトは依然として のインスタンスですB。
クラス A { T foo () { /* ... */ }; }
クラス B はAを拡張します{ }
この Python の例では、クラスはメソッドとプロパティをそのフィールドのオブジェクトにB転送します。これらを(のインスタンス) で使用することは、これらを(これらが転送される のインスタンス)で使用することと同じです。
fooxabBb.aA
クラス A :
def __init__ ( self , x ) -> None :
self . x = x
def foo ( self ):
print ( self . x )
クラス B :
def __init__ ( self , a ) - > None :
self.a = a
def foo ( self ):
self . a . foo ()
@property
def x ( self )
: self.a.xを返す
@x .セッター
def x ( self , x ):
self . a . x = x
@x .削除子
def x ( self ):
del self . a . x
a = A ( 42 )
b = B ( a )
b . foo () # '42' を出力します。
b . x # 値 '42' を持ちます
b . x = 17 # bax の値は 17 になりました
del b . x # bax を削除します
単純

このJava の例では、Printer クラスにメソッドがありますprint。この print メソッドは、印刷自体を実行するのではなく、クラスのオブジェクトに転送しますRealPrinter。外部からはPrinterオブジェクトが印刷を実行しているように見えますが、RealPrinter実際に作業を行うのはオブジェクトです。
転送とは、単に義務を他の誰か/何かに渡すことです。簡単な例を以下に示します。
class RealPrinter { // 「受信機」void print () { System . out . println ( "Hello world!" ); } }
class Printer { // 「送信側」RealPrinter p = new RealPrinter (); // 受信側を作成void print () { p . print (); // 受信側を呼び出す} } public class Main { public static void main ( String [] arguments ) { // 外部からは、Printer が実際に印刷しているように見えます。Printer print = new Printer ( ); print . print (); } }
複雑な
より複雑なケースは、インターフェースを使用することで転送をより柔軟かつ型安全にできるデコレータ パターンです。ここでの「柔軟性」とは、転送の切り替えが から抽象化されているため、 がまたはを参照する必要がないことを意味します。この例では、クラスはインターフェースを実装する任意のクラスに転送できます。クラスには、別の転送元に切り替えるメソッドがあります。句を含めると、各クラスがインターフェースのメソッドを実装する必要があるため、型の安全性が向上します。主なトレードオフは、コードが増えることです。
CABCCICimplements
interface I { void f (); void g (); } class A implements I { public void f ( ) { System.out.println ( " A:doing f()" ); } public void g ( ) { System.out.println ( " A: doing g()" ); } } class B implements I { public void f ( ) { System.out.println ( "B : doing f()" ) ; } public void g ( ) { System.out.println ( "B:doing g ()" ) ; } } //実行時に実装オブジェクトを変更 (通常はコンパイル時に実行) class C implements I { I i = null ; //転送public C ( I i ) { setI ( i ) ; } public void f ( ) { i.f ( ) ; } public void g () { i.g ( ); } // 通常の属性public void setI ( I i ) { this . i = i ; } } public class Main { public static void main ( String [] arguments ) { C c = new C ( new A ()); c . f (); // 出力: A: f() を実行c . g (); // 出力: A: g() を実行c . setI ( new B ()); c . f
(); // 出力: B: f() を実行c . g (); // 出力: B: g() を実行} }
アプリケーション
転送は多くの設計パターンで使用されています。[2]転送はいくつかのパターンで直接使用されています。
- 責任連鎖パターン
- デコレータ パターン: デコレータ オブジェクトは独自のメンバーを追加し、他のメンバーをデコレートされたオブジェクトに転送します。
- プロキシ パターン: プロキシ オブジェクトはメンバーの使用を実際のオブジェクトに転送します。
転送は他のパターンでも使用できますが、多くの場合、使用方法は変更されます。たとえば、あるオブジェクトでメソッドを呼び出すと、別のオブジェクトで複数の異なるメソッドが呼び出されます。
参考文献
- ^ abc Büchi, Martin; Weck, Wolfgang (2000). 「Generic Wrappers」(PDF) . ECOOP 2000 —オブジェクト指向プログラミング. コンピュータサイエンスの講義ノート。第 1850 巻。pp. 212–213。doi : 10.1007/3-540-45102-1_10。ISBN 978-3-540-67660-7。
- ^ ガンマ、エリック、ヘルム、リチャード、ジョンソン、ラルフ、ブリサイドス、ジョン(1995)。デザインパターン: 再利用可能なオブジェクト指向ソフトウェアの要素。Addison -Wesley。Bibcode : 1995dper.book..... G。ISBN 978-0-201-63361-0。
