メッセージ転送
From Wikipedia, the free encyclopedia
例
ここでは、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が返される"