このビデオでは Pharo の先進的な 側面に注目します。
ライブプログラミングに必要な リフレクティブな操作です。
クラスを再コンパイルする時に 何が起きるのかを見ていきましょう。
そしてライブプログラミングを 実践するために
必要なリフレクティブな操作を 説明します。
ここに典型例があります。
我々は通常はまずクラスを定義して
それからメソッドを追加して そのクラスのインスタンスを作ります。
そしてクラスを再定義して 新しいインスタンス変数を追加します。
では、属性が 1 つ減ったら
既に存在しているインスタンスは どうなるのでしょう？
元のインスタンスを新しいクラスに マイグレーションする
メカニズムが必要です。
それで動き続けます。
クラスの動的な再定義や メソッドの再コンパイルを可能にして
インスタンスを新しいバージョンのクラスに
自動的かつ透過的に
マイグレーションさせるための
操作を見ていきます。
そのためにはまず、特定のクラスの 全てのインスタンスを得る必要があります。
それから、それらのオブジェクトを 指していたポインタを
新しいオブジェクトを指すように 全て置き換えていきます。
いいですか？
まずは、あるクラスの全てのインスタンスを どうやって集めてくるかです。
クラスに allInstances メッセージを送ると
その全てのインスタンスが得られます。
通常のオブジェクトのコレクションです。
Window クラスに送れば Window オブジェクトのコレクションが得られます。
最初の要素を取り出して close メッセージを送ることができます。
close メソッドは Window クラスで 定義されているので。
リフレクティブなプリミティブには pointersTo もあります。
このメッセージをオブジェクトに送ると そのオブジェクトへの参照を持つ
全てのオブジェクトのコレクションを 得ることができます。
オブジェクトを入れ替えたい時には
とても便利です。
ポインタを入れ替えるプリミティブは become: です。
2 つのオブジェクトを 入れ替えることができます。
2 つのオブジェクトを対称的に入れ替えます。
システム中のこのオブジェクトを指す全て
この場合は ポインタが 2 つですが
それらは、このオブジェクトを指すように 置き換えられます。
こちら側のポインタは全て破棄されて
オブジェクトが入れ替えられます。
become: はシステム中のポインタを 対称的に入れ替えるということです。
ここに例題があります。
pt1 は
点オブジェクト 0@0 を指して 変数 pt2 も
その 0@0 オブジェクトを指します。
さらに、変数 pt3 は 点 100@100 を指します。
そして、pt1 become: pt3 と書きます。
変数 pt1 として示されるこのオブジェクト を指していたものは
pt3 を指すようになりました。
つまり、pt1 への全てのポインタが pt3 を指しています。
変数 pt2 は pt1 と同じオブジェクトを 指していましたが
pt3 で参照されていたオブジェクトを 指しています。
pt3 については、対称的なので pt1 が指していたオブジェクトを指します。
一番上のものです。
そして pt1 は pt3 だったものを 指しています。
つまり、ポインタが 対称的に入れ替わりました。
非対称な入れ替えは becomeForward: です。
ポインタの入れ替えをするのですが
このオブジェクトを指していたものは こちらのオブジェクトに置き換わり
逆には働きません。 このオブジェクトへのポインタはそのままです。
別の例があります。
becomeForward: を実行すると
pt1 と pt2 に影響が出ます。
pt3 は becomeForard: の 影響を受けません。
このオブジェクトを指していたものは 変化しません。
また別のリフレクティブなプリミティブに adoptInstance: があります。
これはオブジェクトのクラスを入れ替えます。
引数として渡されたインスタンスを クラスに「引き取る」ように頼みます。
クラスを変更することはとても強力な 道具ですが、制限もあります。
そのオブジェクト
この場合 anInstance ですが このオブジェクトの元のクラスが
新しいクラスのフォーマットと 互換性があることが必須条件です。
どんなオブジェクトも入れ替える というわけにはいきません。
インデックス付き、などなど
オブジェクトのフォーマットは とても重要です。
クラスとは何かというと
クラスは本質的にはフォーマットです。
フォーマットがインスタンス変数の数や
可変領域の型を規定します。 前の講義で説明しましたね。
クラスはスーパークラスや メソッド辞書を持っています。
Behavior クラスがあります。 これは Class クラスのスーパークラスで
クラスの基本的な振る舞いを定義しています。
クラスの最小限の基本機能です。
クラスはスーパークラスと メソッド辞書と
フォーマットを持っています。
ここまで話してきた リフレクティブな振る舞いの機能を
具体的な例を挙げながら
まとめてみましょう。
それに沿ってコードを説明していきます。
これらの 3 行で Behavior クラスのインスタンスを作ります。
いいですか、クラスですよ。
Behavior という名前のクラスの インスタンスを作ります。
これを behavior と呼ぶことにします。
このオブジェクトは Model クラスを 継承するように記述します。
こんな風に。
この behavior オブジェクトの フォーマットを設定します。
そして Model クラスのインスタンスを 生成します。
このオブジェクトを model とします。
ここ、このコードのこの行が重要です。
model オブジェクトのクラスを
引数として渡したクラス つまり behavior に変更します。
それまでのインスタンス関係のリンクを切って このクラスのインスタンスにします。
このクラスは Model クラスのサブクラスです。
さて、ここでこのクラスの 新しいメソッドをコンパイルします。
メソッド foo は 2 を返します。 これをコンパイルします。
behavior オブジェクトで。
見ての通り
ここの model オブジェクトに foo メッセージを
こんな風に送ると
2 が返ってきます。
この部分はメソッド探索についてです。
foo メッセージを送ります。
model オブジェクトはそのクラスつまり behavior オブジェクトで探索します。
そしてメソッドが見つかります。
完璧に動作します。
しかし Model クラスのインスタンスを
このようにして作って
foo メッセージを送っても
MessageNotUnderstood エラーが 発生します。
メソッド探索が
そのオブジェクトのクラス つまり Model クラスから探すからです。
foo メソッドは Model クラスや スーパークラスのメソッド辞書では見つかりません。
この高度なメカニズムを使うと Model クラスの特定のインスタンスにだけ
振る舞いを与えることができます。
わかりやすくするために、このやり方を Set クラスにも適用してみましょう。
Set クラスのインスタンス set1 を 生成します。
Set クラスのメソッド辞書には add: メソッドがあります。
Set クラスの特定のインスタンスにだけ add: メソッドを変更したいとします。
特定のインスタンスに 特定の振る舞いということです。
そこで匿名のクラスを作ります。
Set クラスを継承します。
ここにその継承関係があります。
set2 オブジェクトは この匿名クラスのインスタンスです。
その匿名クラスのメソッド辞書で add: メソッドを
set2 に特定の振る舞いで再定義します。
ここで add: メッセージを set2 オブジェクトに送ります。
すると本来とは異なる別の振る舞いをします。
この set1 にメッセージを送っていたら
このメソッドが選択されていたでしょうが それとは異なる振る舞いをします。
つまり、これら 2 つの集合は add: メッセージに別々の反応をします。
ここに演習問題となるコードがあります。
さきほどと同様に Behavior クラスのインスタンスとして
クラスを作ります。
スーパークラスを Set クラスとします。
フォーマットを設定します。
この匿名クラスで add: メソッドをコンパイルします。
これで Set クラスで定義されている add: メソッドを再定義します。
Transcript show を使って add: メソッドの実行を表示します。
それから super を使って Set クラスでの add: メソッドを実行します。
では、1 つ目の集合を作って テストしてみましょう。
Set クラスはここにあります。
このクラスの最初のインスタンスを生成して set とします。
そのオブジェクトに add: メッセージを送ります。
Set クラスの add: メソッドが 使われます。
ここで set のクラスを尋ねると Set が得られます。
しかし、ここでこのプリミティブを 実行します。
set オブジェクトのクラスを変更して
前の方で Behavior クラスを使って
Set クラスのサブクラスにします。
これでクラスが変更されて add: メソッドの新しいバージョンを持った
logClass のインスタンスになります。
ここで、このオブジェクトに add: 2 メッセージを送ります。
するとこの add: メソッドが選ばれて
このコードが実行されます。
Transcript を見て
このメソッドが実行されたか確認できます。
2 は追加されます。
super を使って Set クラスの add: メソッドを使うからです。
こうして特定の集合を スパイすることができます。
この行を使って。
まとめると
これらのリフレクティブな操作を使って とても画期的なツールを作ることができます。
新しい機能を実装して テストすることができます。
これらのリフレクティブなプリミティブで 言語拡張をすることができます。
ただし、リフレクションを使う上での 黄金律があります。
ドメインコードで不適切な使い方を してはいけない、ということです。
ドメインコードでリフレクティブなコードを 使うことは制限すべきです。
リフレクションを使うと カプセル化を破ることができます。
本当に
オブジェクトプログラミングの規律を 踏み越えることができてしまいます。
したがって、ツール構築に使うに 留めておくべきです。