メッセージ転送
メッセージ転送(メッセージてんそう、英語: message forwarding)とは、オブジェクト指向言語において、オブジェクトに対して送られたメッセージを送信対象となっていないオブジェクトやメソッドに転送できる機能のことである。オブジェクト指向言語の中ではSmalltalkにおいて初めて導入された。
概要
[編集]純粋な(メッセージ指向の)オブジェクト指向言語において、メッセージ送信は比喩ではなく実在する機構である。メッセージは、セレクターと引数をオブジェクト等でひとまとまりにした値の塊であり、メッセージを受信したオブジェクトはメッセージを解析してメソッドの選択や起動を行う。ここで通常であれば、オブジェクトはメッセージに含まれるセレクターを元に自らが保有するメソッドを調べ、セレクターに一致するメソッドを起動する。しかし、セレクターに該当するメソッドが存在しなかった場合は、メッセージ処理用のメソッドにメッセージを転送し、プログラマによるメソッドの処理を試みるのである。
例
[編集]ここでは、Smalltalkによるメッセージ転送の記述例を示す。Smalltalkではメッセージを受け取ったオブジェクトがセレクターに該当するメソッドを保持していない場合、受け取ったメッセージを「doesNotUnderstand:」メソッドに転送する。次に「doesNotUnderstand:」メソッドのみ登録した「Example」クラスを使用して具体例を示す。
クラス定義:
"ExampleクラスをSmalltalk環境に登録する"
Object
subclass: #Example "ExampleはObjectクラスから派生させる"
instanceVariableNames: '' "オブジェクトに所属する変数は定義しない"
classVariableNames: '' "クラスオブジェクトと共有する変数は定義しない"
poolDictionaries: '' "クラスに所属する変数は定義しない"
category: 'UserObjects'. "クラスの分類はUserObjectsとする(今回の名前に意味はない)"
"Exampleに登録するdoesNotUnderstand:メソッドの登録"
Example methodsFor: 'message forwarding'
!
doesNotUnderstand: aMessage
"メッセージのセレクターがhelloであるか判定する"
( aMessage sends: #hello )
ifTrue:
[
"セレクターがhelloであれば次の文字列を返す。"
^'Selector is hello'.
]
ifFalse:
[
"セレクターがhello以外なら次の文字列を返す。"
^'Selector is not hello'
]
!!
メッセージ送信:
| example |
example := Example new.
example hello. "'Selector is hello'が返される"
example goodbye. "'Selector is not hello'が返される"
上記の例は、「hello」とそれ以外のメッセージをExampleが生成したオブジェクトが受け取り、オブジェクトがメッセージ中のセレクターを基準に処理を切り替えている例である。このようにSmalltalkではメッセージ送信が比喩ではなく具体的な機構である事を利用し、オブジェクト宛に送信されたメッセージを自在に制御することができる。
なお、今回メッセージ中のセレクター情報( hello )しか使用していないが、メッセージからは引数情報も参照できる。また、「aMessage sendTo: 送信先のオブジェクト」と記述することでメッセージを別のオブジェクトに送りつけることもできる。
用途
[編集]- (1)全てのメッセージを無視するNullオブジェクトを作る
- (2)上記を拡張し、必要なメッセージだけを処理するイベントハンドラーを作る
- (3)受け取ったメッセージをフィールドに委譲し一種の多重継承を実現する
- (4)フィールドへの委譲を利用し、動的な継承を実現する
- (5)全てのオブジェクトに対し使用可能なProxyオブジェクトを作る
- (6)メッセージを遅延送信できる
(4)(5)(6)について補足する。
(4)は、スーパークラスにあたるオブジェクトを実行時に交換できるという事である。古いインタフェースの互換性維持などに使用できる。例えばlineToなど単純なPath命令しか備えていないPathクラスと、Pathクラスを使用するVectorImageクラスが存在したとする。ある改修に伴いVectorImageがPathクラスは、drawEllipseを要求するようになった。この時、互換性の面からPathクラスのコードは変更できず、PathクラスにdrawEllipseを追加できないとする。
この様な場合、Pathクラスを継承しdrawEllipseを登録したPathExtendを作成するか、Pathクラスへのメッセージを全て委譲し、更にdrawEllipseを登録したPathExtenderを作成する手がある。継承を使用した前者の場合、Pathクラスと同じInterfaceを持ったクラスの整合性を保つため大量のXxxExtendクラスを作成する事になってしまう。委譲を使用する後者の場合、委譲すべきメッセージが50あるなら50個のメソッドを登録するはめになる。
メッセージ転送が可能な場合では、後者の委譲の方法を採用しつつも、 委譲処理をメッセージ機構に任せる事で、PathExtenderにはdrawEllipseとdoesNotUnderstand:メソッドを登録するだけで済ませることができる。
(5)は、クラスのインタフェース毎にスタブコードを書かずとも遠隔手続き呼出し (RPC) などが可能という事である。C++系統の言語であればRPCの際、一つの関数により一つのオブジェクトに対する全てのメンバー関数呼び出しの内容を横取りできる機能がないため、インタフェース毎にスタブコードを作成しなければならない。 メッセージ転送が可能な場合においては、1つのオブジェクトに対する全てのメッセージを横取りできるため、スタブとなるプロキシクラスを通信方法に合わせ1種類用意しておけば良い。
また、通信方法もメッセージオブジェクトを直列化して送信先に送り、送信先でメッセージオブジェクトに復元して送信先のオブジェクトに送りつければよいので単純になる。
(6)は、メッセージを記録してUndoやRedo、スレッド間通信などに使えるという事である。Smalltalkでは、メッセージを遅延実行するため、メッセージを保存するためのMessageCatcherクラスが用意されている。
| message |
message := ( MessageCatcher new ) size. "sizeメッセージを保存"
message sendTo: 'ABCDEF'. "文字列にメッセージを送信し6が返される"
メッセージ転送をサポートする言語とライブラリ
[編集]言語
[編集]- Smalltalk
- オブジェクトにセレクターで指定されたメソッドが存在しない場合「doesNotUnderstand:aMessage」が呼び出される。
- Common Lisp
- オブジェクトにメソッドが適用できない場合「no-applicable-method」が呼び出される。また、オブジェクにスロットが存在しない場合「slot-missing」が呼び出される。
- Objective-C
- オブジェクトにセレクターで指定されたメソッドが存在しない場合「(void)forwardInvocation:(NSInvocation*)invocation」が呼び出される。
- Ruby
- オブジェクトにセレクターで指定されたメソッドが存在しない場合「def method_missing( selector, *arguments, &block )」が呼び出される。Smalltalkと異なりセレクターや引数がひと塊のメッセージで受け取れず、それぞれ個別に受け取る。また、ブロックを受け取ることができる。
- PHP
- オブジェクトにセレクターで指定されたメソッドが存在しない場合「function __call( $selector, $argument )」が呼び出される。blockを受け取らない点を除き、Rubyと同様である。
- Python
- 正式にメッセージ転送専用の機能を備えていない。オブジェクトにセレクターで指定されたメソッドが存在しない場合「def __getattr__( this, selector )」が呼ばれるため、クロージャーと組み合わせることにより模倣することができる。また、「def __getattribute__( this, selector )」を定義していれば、セレクターに指定したメソッドの有無に関わらずメッセージ要素を取得できる。
ライブラリ
[編集]- COM
- 「IDispatch」インターフェイスを実装している場合、メソッドの選択を自前で実装する必要があるため必然的にメッセージ転送処理を記述する必要がある。
- Windows API
- Microsoft Windowsでは、ウィンドウがオブジェクトとして振る舞う。そしてウィンドウのメッセージ処理(ウィンドウプロシージャ[1])において、
SendMessage()
関数[2]あるいはPostMessage()
関数[3]などを用いて他のウィンドウにメッセージを同期/非同期転送することができる。PostThreadMessage()
関数[4]を用いて、メッセージループを持つスレッドにメッセージを非同期転送することもできる。