やあ、みなさん、このビデオでは クラスの上級編をお見せします。
3 つのことをします。
1 つ目は、クラスやそのサブクラスの インスタンス間での状態の共有です。
2 つ目は、クラス自身に変数を 持たせる方法です。
3 つ目は、クラスの初期化です。
まず最初に、インスタンス間で 状態を共有する方法です。
ここにクラスと いくつかのインスタンスがあります。
状態を共有するためには、これらの インスタンスに共通するオブジェクトが必要です。
Java では、「静的(static)」な インスタンス変数を使います。
静的な変数の値は
全てのインスタンスで同じです。
Pharo ではクラス変数を使います。
これはクラスを作るときの 見慣れた表現式ですね。
ここでは Color クラスを作っています。 Object のサブクラスです。
まず、インスタンス変数を列挙していて その次の行を見ると
Color クラスのクラス変数を 列挙しています。
Color クラスには少なくとも2つの変数があります。 ColorRegistry と ComponentMask です。
クラス変数の値は
そのクラスとサブクラスの 全てのインスタンスに共有されます。
これらの変数はインスタンスメソッドからも クラスメソッドからもアクセス可能です。
クラス変数は大文字で始まります。
例題に戻りましょう。
Color クラスは
メタクラス Color class の インスタンスです。
Color クラスでは 2 つのインスタンス変数 が定義されています。grb と alpha です。
これらのインスタンス変数はプライベートです。
つまり、Color クラスのメソッドからしか アクセスできません。
ColorRegistry も定義されています。 これは下線が引かれ大文字になっています。
つまりこの変数は共有されている ということを示しています。
この変数はクラス変数で
Color クラスや Color class クラスの メソッドからアクセス可能です。
この変数を使った例があります。
Color クラスの privateBlue という インスタンスメソッドでは
クラス変数を名前でアクセスしています。
同様に、この変数に値を代入するには
:= を使います。他の変数と同じです。
インスタンス側でもクラス側でも可能です。
よくある使い方は
クラス変数の値が
インスタンスメソッドで読まれて
クラスメソッドで書き込みます。
よく使われるやり方ですが そうしなければならないというわけではありません。
では、クラスのインスタンス変数を 見てみましょう。
これはこれで別物です。
クラスはオブジェクトです。 他のオブジェクトと同様に。
他のオブジェクトと同様に クラスもインスタンス変数を持つことができます。
メタクラスがクラスを定義して インスタンス変数を列挙します。
それらのインスタンス変数はそのシングルトンである クラスからのみアクセスできます。
クラス側にインスタンス変数を定義するには
コードブラウザの 「class」ボタンをクリックします。
するとこの表現式が出てきます。
今、RPackageSet クラスを ブラウズしていますが
このクラスのメタクラスは cachePackages という名前の変数を定義しています。
こういった変数はクラスメソッドからのみ アクセスすることができます。
そして必ず小文字で始まります。
例題があります。
変数 cachePackages は メタクラス RPackageSet class で
定義されています。 つまり
RPackageSet クラスはメタクラス RPackageSet class のインスタンスですが
この変数に関連付けられた値を持っています。
同様に
RPackageSet クラスの全てのサブクラスは
この変数の それぞれの異なる値を持っています。
共有されていません。
RPackageSet クラスやそのサブクラスは
この変数にそれぞれの値を持っています。
クラス変数とクラス側のインスタンス変数は 別のものです。
シングルトンパターンを使って説明します。
シングルトンパターンの目的はクラスのインスタンスが 1つだけであることを保証することです。
このパターンを実装する方法の 1 つは 変数にインスタンスを格納して
そのインスタンスへのアクセスを提供して インスタンス生成を無効化することです。
WebServer でそれをやってみます。
ここでクラス側の インスタンス変数を使います。
名前は小文字になっています。
この変数はメタクラスの定義で 宣言されています。
そして new メソッドの実行を 無効化しています。
WebServer クラスに新しいインスタンスを 作るようにメッセージを送ることはできません。
uniqueInstance を経由するように 強制されています。
変数の値が nil でなければ その値が返されて
nil であれば super new を使って その変数にインスタンスを与えます。
WebServer クラスの 新しいインスタンスを生成して
変数 uniqueInstance に格納します。
実際にクラスインスタンス変数を使うと
次のような結果になります。
WebServer クラスの各サブクラス毎に
それぞれ異なるインスタンスが
uniqueInstance に格納されます。
つまり、WebServer クラスに サブクラスが 3 つあれば
変数 uniqueInstance には 合計 4 つの値があることになります。
WebServer クラスに 1 つと 各サブクラスに 1 つずつです。
クラス変数を使うと
このクラスのインスタンス側を編集して
UniqueInstance とします。 U は大文字です。
先ほどと同様に new を無効化して UniqueInstance を実装します。
違いは U が大文字だということです。
クラス変数を使った結果
このクラス階層で唯一のシングルトンを 持つようになります。
WebServer クラスに サブクラスが 3 つあるとすると
WebServer とその 3 つのサブクラスが 同じシングルトンを共有することになります。
したがって全体で 1 つの インスタンスを持つことになります。
では、クラスの初期化についてです。
クラスやインスタンスの間で共有される 変数を持つことができますが
いずれかの時点で 値を与えてあげる必要があります。
クラスを初期化する時です。
オブジェクトは initialize メッセージが 送られた時に初期化されます。
同様にクラスへ initialize メッセージを 送ることができます。
initialize メソッドのコードで どのようにして変数を初期化するかを決めます。
Color クラスを初期化したい場合は
Color クラスに initialize メッセージを送ります。
典型的にはバージョン管理システムが
システムにクラスをロードした時に
initialize メッセージが送られます。
これは自動的に行われます。 ロードされたクラスそれぞれに
手動でメッセージを送る必要はありません。
しかし、クラスを実装する時に
新しいクラスを式で定義した時には
initialize メッセージを手動で 送る必要があります。
ここに Color クラスの例があります。
メタクラス側を見ています。
いくつかのクラス変数や
クラス側のインスタンス変数を 初期化しています。
ここには両方のタイプの変数があります。
initialize メソッドでは
クラス側では super initialize は 使いません。
インスタンス側で initialize メソッドを追加する場合には
super initialize を使って系統的に
新しく作られたオブジェクトの 全てのインスタンス変数を初期化します。
しかしクラスの場合には
initialize メッセージが送られた時点で 既にクラスやスーパークラスは存在しているので
クラス側のinitialize メソッドでは super initialize はしません。
さもないと クラスを何度も初期化してしまいます。
覚えておくべきポイントです。
状態を共有するためにはクラス変数を使います。
クラスはオブジェクトで それぞれの変数を持つことができます。
クラス側にもインスタンス変数があります。
クラスを初期化するためには initialize メッセージを送って
クラス側の initialize メソッドを 適用します。