こんにちは このビデオでは Pharo の重要な機能である
doesNotUnderstand: フックを説明します。
もうデバッガーウィンドウで このメッセージを見ましたよね。
それはどこから来て 何をするものでしょう？
例を見てみましょう。
オブジェクト node1 があります。
このオブジェクトに coucou: stef メッセージを送ってみます。
node1 から Object クラスまで
メソッド探索していきます。
coucou: メソッドは このクラスでは見つかりません。
そこでスーパークラスへ行きます。 しかし、ここにもありません。
仮想的に探索しても見つからないので
そのメッセージを具体化します。 具体化（レイフィケーション）については
イントロスペクションとリフレクション のビデオで議論しました。
具体化とは、暗黙的な概念を オブジェクトとして表現することです。
ここにメッセージがあります。
このメッセージを表現する オブジェクトを生成します。
Messageクラスのインスタンスです。
仮想システムはオブジェクト node1 に
メッセージを再送します。
doesNotUnderstand: メッセージを さきほどのオブジェクトを引数として送ります。
これでメッセージ探索を新たに実行します。
doesNotUnderstand: メソッドは Node クラスでは見つかりません。
そこでスーパークラスを辿って探します。
DNU（doesNotUnderstand:）メソッド が見つかります。
これでそのメソッドを実行できます。
doesNotUnderstand: は
メッセージが失敗した時に
システムがあなたに送るメッセージです。
どんなクラスでもこのメソッドを再定義して
メッセージを理解できなかった時の 特殊な振る舞いを定義できます。
このメソッドを使って色々な機能を 構築することができる
とても重要な道具です。
自動的な移譲（デリゲーション）や
分散プログラミングなどに有用です。
ここで doesNotUnderstand: の 利用例をいくつか見てみましょう。
受け取った全てのメッセージを 何か別のオブジェクトに転送したいとします。
単純な移譲としては メッセージを転送する対象を
格納するオブジェクトを作ります。
doesNotUnderstand: メソッドを 再定義します。
引数として aMessage オブジェクト が渡されて
その中に失敗したメッセージの セレクターが格納されています。
そこでこのメッセージに 再送するようお願いします。
sendTo: self target とすると
別のオブジェクトにそのメッセージが 再送されます。
コードの可読性を壊してしまうような
強力な機能なので、注意してください。
ここに説明している通りです。
このメッセージが最終的に誰が受け取るか このコードに書いてあります。
ツールや高度な仕組みを実現する上で
とても有用な機能です。
もう 1 つの例として ログ取りプロキシを見てみましょう。
どんなものかというと
メソッドを極少数しか持たない 小さなオブジェクトを作って
doesNotUnderstand: メソッドを 変更します。
そしてドメインオブジェクトの代わりに
このプロキシオブジェクトを become: で忍び込ませます。
まず、プロキシオブジェクトを作ります。
プロキシオブジェクトに 元オブジェクトを与えます。
元オブジェクトというのは このプロキシで置き換える
対象のオブジェクトです。
invocationCount をインクリメントして メッセージを数えます。
まだメッセージを受け取っていないので カウンタを 0 に初期化します。
そして元オブジェクトを与えます。 この self は 後で入れ替わります。
ここでこのプロキシの DNU メソッドを再定義します。
理解できないメッセージを 受け取るたびに
それがトランスクリプトに表示され カウンタをインクリメントしていきます。
そしてそのメッセージを 元オブジェクトに転送します。
前の例と同様 メッセージを別のオブジェクトに再送します。
Messageクラスが実装している sendTo: の代わりに、見ての通り単に
perform:withArguments: メソッドを 使います。
もうこれについては前の講義で 説明しました。
例を見てみましょう。
このログ取りプロキシの使い方は
まず point オブジェクトを生成して
それから become: して
この point オブジェクトを指している すべてのものを
ログ取りプロキシを生成して
それにすり替えます。
point オブジェクトにメッセージを送ると
become: したので実際には ログ取りプロキシになっていて
メッセージを送るたびに
トランスクリプトに表示して
カウンタがインクリメントされます。
doesNotUnderstand: に書いた通りです。
最後にカウンタは 3 になります。
最後の例で見た通り このプロキシフレームワークには
制限があります。
例えば、そのオブジェクトが self に 送るメッセージを捕まえることはできません。
これはちょっとトリッキーです。
クラスには become: は使えません。 リフレクションには制限があります。
プロキシと元オブジェクトが 同じメッセージを受け付ける場合にも
リスクがあります。
プロキシにメッセージを送った時に
そのまま応答してしまい DNU で捕まえて元オブジェクトに
転送することができません。
Pharo には全てトラップすることができる プロキシフレームワークが別にあります。
しかし、そういったものは この簡単な実装例よりも複雑です。
この技術の他の応用例としては
アクセサやメソッドを動的に生成する というものがあります。
ここに、再定義した doesNotUnderstand: メソッドがあります。
メッセージを受け付けると
対応するインスタンス変数がないか確認して
もしあれば、新しいメソッドを生成して compile: を使って
そのクラスの新しいメソッドとします。
これは変数の値を返します。
ここで変数の リードアクセサを生成しています。
もしメッセージにマッチする
インスタンス変数がない場合には super doesNotUnderstand: を送ります。
これを使って 実際に呼ばれたかどうかに従って
自動的にリードアクセサを定義できます。
まとめると
小さなオブジェクトの使い方を 見てきました。
Object クラスのインスタンスではなく
protoObject クラスのインスタンスです。
プロキシの基盤として使うことができます。
doesNotUnderstand: を再定義することで
メッセージの失敗を捕捉して 再送することができます。
この強力なフックは Pharo の多くのツールの基盤になっています。
しかし、非常に注意深く使ってください。
本当に必要な時だけ使ってください。
ドメインコードには使わないように。 とても高度な技術です。