このチュートリアルは,読者が既にオブジェクト指向プログラミングに親しんでいることを前提として,OTcl によるプログラミングを速やかに開始できるように用意されたものである。ここではリファレンス Objects in OTcl や Classesin OTcl から得られる言語の多くの詳細は無視している。また,C API や autoloaded クラスの書き方についても言及していない。
% Class Bagel Bagelこれでベーグルを作って,それを info コマンドで追跡できるようになった。
% Bagel abagel abagel % abagel info class Bagel % Bagel info instances abagelもちろんベーグルはまだなにもしない。それらは,焼かれたのかどうか記憶しているべきだ。set コマンドによってインスタンス変数を作り,アクセスすることが可能だ。すべてのインスタンス変数は C++ でいうところの public だ。もう一度 info コマンドを使ってこれらのことを見てみることにしよう。
% abagel set toasted 0 0 % abagel info vars toasted % abagel set toasted 0しかし,我々が本当に望むのは,ベーグルが最初は焼かれていない状態にあることだ。これは Bagel クラスに init instproc を加えることで達成できる。一般的に,新しいオブジェクトが初期化されるべき場合には,それらのクラス用に init instproc を書けばよい。
% Bagel instproc init {args} { $self set toasted 0 eval $self next $args } % Bagel bagel2 bagel2 % bagel2 info vars toasted % bagel2 set toasted 0ここで述べておくことがいくつかある。オブジェクトを作る段階で,シス テムは,それらが生成され(割り当てられ)た直後に,init が呼ばれるよ うに手配する。instproc メソッドは Bagel クラスで使えるようにメソッ ドを追加した。このメソッドの名前は init なので,システムは新しいベー グルが作られた場合に,それを見つけ出し,呼び出すのである。
init instproc にはまたいくつかの面白い特徴がある。next の呼び出しは init メソッドには典型的なものであり,すべての継承された init メソッ ドを派生した init メソッドに結びつけるものである。これに関しては後 で詳しく述べる。self という名前の変数はメソッドが呼び出された時にセッ トされ,それが動作中のオブジェクトの名前が入っている。この例の場合, bagel2 が入っている。これは C++ における this に似ており,そのオブ ジェクト上の他のメソッドやオブジェクトクラスを通して継承されたメソッ ドを使うために使用される。また,興味深い二つの特別な変数 proc と class がある。
いま,我々のベーグルは,init を書く前につくった最初のものを除き,そ れが焼かれたかどうかを記憶している。最初のものを壊して,再び始める ことにしよう。
% Bagel info instances bagel2 abagel % abagel destroy % Bagel info instances bagel2 % Bagel abagel abagelこれで,ベーグルにそれらをトーストするためのメソッドを加える準備が できた。インスタンスによって使用されるためにクラスに保存されている メソッドは instproc と呼ばれる。それらは普通の Tcl の手続き (proc) と同様に,引数リストと本体をもっている。次に toast instproc を示そ う。
% Bagel instproc toast {} { $self instvar toasted incr toasted if {$toasted>1} { error "something's burning!" } return {} } % Bagel info instprocs init toasttoasted 変数のセットは別として,toast instproc は instvar メソッド の使用例を示している。instvar メソッドはインスタンス変数の宣言とそ れにローカルなスコープを与えるために使用される。インスタンス変数 toasted は set メソッドによって以前に初期化されていたが,ローカル変 数 toasted を通して操作することができる。
我々はベーグルの toast instproc をシステムによって提供されている info や destroy instprocs と同じように呼び出した。つまり,ユーザ定 義のメソッドとシステムメソッドには違いはないのである。
% abagel toast % abagel toast something's burning!これで我々はスプレッドをベーグルに塗って味わうことができるようになっ た。トッピングされたベーグルはもちろん,もしトッピングされていない ベーグルがあったら,トッピングできるベーグルを別のクラスとして作り たくなるだろう。これらの二つのクラスを使って「継承」に関して調べて みよう。まずは,Bagel から継承される新しいクラス SpreadableBagel を 作ることから始めよう。
% Class SpreadableBagel -superclass Bagel SpreadableBagel % SpreadableBagel info superclass Bagel % SpreadableBagel info heritage Bagel Object %info メソッドのさらなるオプションによって SpreadableBagel が本当に Bagel から継承されたものであるか,また Bagel もが Object から継承さ れているかどうか判定できる。Object にはすべてのオブジェクトの基本的 機能が盛り込まれている。これらの機能は新しいクラスがデフォルトで継 承する。つまり,Bagel は Object から直接継承をしており, SpreadableBagel は Bagel 経由で間接的に Object を継承している。
"-superclass" によるクラス生成 のための文法に関しては,さらなる説明 が必要だ。まず,読者はクラス生成 (create) 以外のすべてのメソッドが どうしてオブジェクト名の後のメソッドの名前でよばれるのか困惑してい るだろう。その答えは,クラス生成 (create) は,他のメソッドがたとえ 見つからなくてもシステムが感知しないメカニズムで呼ばれるからという ことだ。これは良く知られたウィジェットライクなクラス生成文法を提供 するために行われている。しかし,あなたが望むなら create を明示的に 呼び出すこともできる。
次に,オブジェクトの初期化の段階で,引数の各ペアは,対応する引数と ともにオブジェクトを呼び出すために(名前の頭にダッシュ(-)がついた) 手続きの名前に書き換えられる。この初期化機能は Object クラスの init instproc によって行われる。これが Bagel の init instproc が next を 呼ぶ理由である。以下の二つのコードの切れ端は(戻り値を除いて)同じ 意味をもつ。簡略型の記法が通常用いられるものであり,長い方の書き方 は簡略型の記法で実際に行われることを示している。
% Class SpreadableBagel SpreadableBagel % SpreadableBagel superclass Bagel % Class create SpreadableBagel SpreadableBagel % SpreadableBagel superclass Bagel % Class SpreadableBagel -superclass Bagel SpreadableBagelこの関係を理解すれば,オブジェクトの生成に何も特別なことはないと分かるだろう。例えば,ベーグルのサイズを byte (いわゆるバイトではなく噛む回数)で指定するようなオプションを加えることもできる。
% Bagel instproc size {n} { $self set bites $n } % SpreadableBagel abagel -size 12 abagel % abagel set bites 12我々は,SpreadableBagel にトッピングを塗り付けるためのメソッドを加 えなければならない。それには現在のトッピングのリストも必要だ。常に 空のトッピングのリストから始めるということにするのなら,init instproc も必要だ。
% SpreadableBagel instproc init {args} { $self set toppings {} eval $self next $args } % SpreadableBagel instproc spread {args} { $self instvar toppings set toppings [concat $toppings $args] return $toppings }ここで init メソッド中での next の使い方について,もう少し説明しよ う。SpreadableBagel もベーグルであり,それらの toasted 変数もゼロに 初期化される必要がある。next メソッドの呼び出しによって,継承木を上 にたどった次のメソッドが見つかり,呼び出される。これは CLOS の call-next-method と同様の機能を提供している。
この場合,Bagel クラスの init instproc が見つかり,呼び出される。 eval は args の中にある引数リストをフラットにするためにするためだけ に用いられている。Bagel の init instproc 中で next が再び呼ばれた場 合,Object の init メソッドが見つけられ,呼び出される。それはその引 数を手続き名と引数値のペアに書き換えて,順番にそれぞれを呼び出して, すべてのオブジェクトのオプション初期化機能を提供する。init instproc 中で next の呼び出しを忘れてしまうと,オプションの初期化が行われな い。
ベーグルに taste instproc を加えよう。その機能を二つのクラスに分け て,next によって結合することにする。
% Bagel instproc taste {} { $self instvar toasted if {$toasted == 0} then { return raw! } elseif {$toasted == 1} then { return toasty } else { return burnt! } } % SpreadableBagel instproc taste {} { $self instvar toppings set t [$self next] foreach i $toppings { lappend t $i } return $t } % SpreadableBagel abagel abagel % abagel toast % abagel spread jam jam % abagel taste toasty jamもちろん,ゴマやオニオン,ケシ,他のベーグルの大群が次々にやって来 たら,我々の方法を拡張しなくてはいけない。我々はインスタンス変数に よって味付けを見張ることができたが,これは適切ではないだろう。味は ベーグル生来の特性だが,他の味にも影響を与える。オニオンベーグルに ジャムは塗らないだろう? クラス階層構造を作る代わりに多重継承によっ て,味のクラスの mixn を作ろう。これらはベーグルやその他の混ぜあわ される食材の味の依存特性を与えるものだ。
% Class Sesame Sesame % Sesame instproc taste {} { concat [$self next] "sesame" } % Class Onion Onion % Onion instproc taste {} { concat [$self next] "onion" } % Class Poppy Poppy % Poppy instproc taste {} { concat [$self next] "poppy" }まだこれらは十分に機能しそうにはない。しかし,ここでの next の使い 方は,これらが自由に混ぜ合わされることを許している。
% Class SesameOnionBagel -superclass {Sesame Onion SpreadableBagel} SesameOnionBagel % SesameOnionBagel abagel -spread butter % abagel taste raw! butter onion sesame多重継承を行うために,システムはローカルなクラス包含関係を提供する ため,線形継承順序を判定する。この順序は info オプションによって調 べることができる。next は結合動作を行う場合に,この順序をだどる。
% SesameOnionBagel info heritage Sesame Onion SpreadableBagel Bagel Object我々の mixin を他のクラス,ベーグルとは全く関係のないクラスとも結合 させて,チップスのファミリーを作ることもできる。
% Class Chips Chips % Chips instproc taste {} { return "crunchy" } % Class OnionChips -superclass {Onion Chips} OnionChips % OnionChips abag abag % abag taste crunchy onion
以下にチュートリアルで触れなかったいくつかの重要な事項を示す。