クラス–オブジェクト関係を写し込む
【以下、"空白"は全て"半角空白"を意味します。なお、以下【】の中は訳者が付け足したものです。】
Mopsのようなオブジェクト指向言語では、 前レッスンの会計士のアナロジーで描写したものと同じ種類の関係を核としてプログラムを構築します。 クラス定義はプログラムの構造において中心的な役割を果たします。 ですから、Mopsプログラムを計画する初期の段階で最も重要なのは、 あなたのプログラムにおける主要なオブジェクト(アクター)が何をするのかを見定めることです。 Mac OSは、非常に多くの異なる現実世界の対象;預金通帳、アーチストのカンバス、カレンダー【等】を スクリーン上のメタファーによって再創造することができるのですから、Mopsオブジェクトのクラスとしては、 現実世界の道具に似た振る舞いを持つものにするのが最善といえます。 そのプログラムの諸クラスが何になるのかをひとたび決めたなら、それらのクラスとメソッドを定義して、ブログラムを書き始めるべきときです。 それから、それらクラスのオブジェクトを生成します。最後に、プログラムを動かすそれらオブジェクトに対するメッセージを書きます。
まず、スクリーン上に長方形オブジェクトを描画することができるクラスを定義して、クラス–オブジェクト関係をMopsプログラムに適用してみましょう。 同時に、それはMopsプログラムが実際にどう見えるのかについての案内ともなるでしょう。 プログラムの行の並べ方、段落の付け方、空白の置き方、大文字化などの物理的な構造に特に注意してください。 Mopsは、あなたがプログラムをどんな見栄えにするのかについては、全く頓着しませんが、 以下に述べるやり方は、あなたがデバッグや拡張のためにコードを印刷したものを読むときの助けとなるでしょう。 これに関連する話題についての深い議論としては、レファレンスパートの3,5および6章も参照してください。
クラスを定義する
会計士クラスメタファーのところで気付いたかもしれませんが、各クラスは、 そのクラスのオブジェクトが、活動のために呼び出されたときに常に従う一連の規則、ないし手続となるものによって定義されていました。 このとき、
クラスを定義することは、それらの規則や手続:メソッドの確立を含意します。
大部分のクラスは、また、そのクラスのオブジェクトに結びつけられる情報(データ)も持っています。 例えば、家計会計士のクラスは、そのクラスのどの会計士も、その仕事に対して報酬を受けるべきことを命ずることができます。 すべての家計会計士(実例として、ジョンとパーシバル)は、その時間給に当たる数字を持っています。 クラス定義は単に、汝、時給を持たむと述べるのみです。 オブジェクトが生成されるときに、その時給率はその変数に組込まれます。 重要なのは、ジョンとパーシバルは全く異なる時給を持ちうるということです。 彼らは自分自身のデータを、同じクラスの他のオブジェクトから切り離して、保管するからです。 そのメソッドの一つが、その時給率を取りだし、それにあなたの税務に費やした時間をかけ算して、 あなたに請求書を送ります。
さて、MopsクラスであるRectの構築がどのようなものであるのかを見てみましょう。 これは、長方形オブジェクトを生成するためのすべての手続を定義することになるでしょう。
Carbon環境では、長方形はスクリーン上の二つの点で定義されます。:その長方形の左上頂点と右下頂点の位置です。【Mopsでは現状では関係ありませんが、OS X CarbonのHIViewで用いられる長方形であるNSRectangleでは左上頂点と縦横のサイズによって定義されます】 言い換えれば、スクリーン上の長方形のどのインスタンスとしても、Rectクラスのオブジェクトは、これら二つの変数を数値で埋める必要があるということです。そのため、これらの変数は
インスタンス変数(instance variables (略してivars))と呼ばれます。 これらは、オブジェクトの定義の中にある格納場所であって、 長方形を描画できるようになる前に無くてはならない必須データ(二つの点)をおくためのものです。
この時点までで、クラス定義は次のようになります。:
:class RECT super{ object }
point TopReft
point BotRight
いくつかのことに気付きます。 まず第一に、クラス定義の始まりが、:class("コロン–クラス"と読みます)となっていて、 コロン":"と"class"の間に空白はありません。 :classとそのクラスの名前との間には少なくとも一つの空白またはタブ文字があります。 私達はRECTを目立つように大文字にしました。まさにそれがここで定義されているのですから。 しかし、これは必要なわけではありません。 Mopsでは大文字と小文字は同じものとして扱われるからです。 あなたの好みに応じて、どんなやり方をしてもかまいません。
クラスの名前と同じ行に、私達の長方形クラスがそこから派生するスーパークラスへの参照があります。 この参照の形式は、super{というワード("super"と左中括弧との間には空白はありません)で始まり、 次にスーパークラスの名前、そして}となっています。 これら【スーパークラス名も含めた】三つの要素は、すべてのMopsワードと同様、空白またはタブ文字で区切られます。 一つのクラスが一つより多いスーパークラスを持つこともできることを、後で見るでしょう。 これは
多重継承として知られています。 これについて、ここでは細部には立ち入りませんが、 ひとことだけいうと、もし複数のスーパークラスが存在するならば、}の前にそれらの名前を順番に 空白またはタブ文字で区切って並べることになります。
この例ではスーパークラス名はObjectですが、 Mopsシステムでのオブジェクトという言葉の一般的な用法と混同しないようにしてください。 オブジェクトという言葉は、一般には、全オブジェクトに一般的に言及するものです。 この特別な場合においては、Objectはひとつのクラスです。 これは少しややこしく思うかもしれませんが、 実際には、私達がこの特殊なクラスを"Object"と名付けたのは、まさに、 私達の"オブジェクト"という語の一般的な用法のゆえなのです。 Mopsにおけるクラスは、すべて、継承先を辿っていくとクラスObjectに至ることができるからです。 ゆえに、クラスObjectはすべてのMopsオブジェクトにとって適切な振る舞いを定義しています。 これが、Objectという名前がこのクラスに適切な理由なのです。
ですから、継承によって、クラスRectはクラスObjectで定義されたすべてのメソッドを利用することができます。 Objectクラスのために定義されているメソッドに関心がある場合は、ソースコードリストを覗いてみてください( ‘PPC source’フォルダの"qpClass"ファイルにあります。)【クラスレファレンスにも説明があります。】
インスタンス変数【宣言】は、このクラスから生成されたオブジェクトのデータエリアにメモリースペースを確保するよう、Mopsに対して告げます。 確保されるべきスペースの量は、インスタンス変数の特性によって決まります。インスタンス変数は、それ自身、別のクラスで定義されています。 ここでは、インスタンス変数(ivar)はTopLeftとBotRightという名前で、 どちらも、クラスPointに属しています。 もしもクラスPointがもっと前に定義されていなかったなら、 Rectクラスでインスタンス変数TopLeftおよびBotRightを生成することはできないでしょう。 運良く、クラスPointは、Mopsの数多くの既定義クラスの一つです。
注意:手続的言語ファンからみると、Mopsのオブジェクト指向を理解するときの鍵は、 次のいくつかのステップにおいて辞書内のスレッドをたどるとき、直接の実行のステップを見ているのではないということです。 むしろ、そのプログラムの後の段階でメッセージが送られるときに初めて解放される一種の潜在エネルギーとして メモリー内に止まっているであろうフレームワークを、あなたは構築しているのです。
クラスRect内に生成されるPointオブジェクト(TopLeftとBotRight) のための規則と手続が何であるかを理解するために、 PointクラスのMopsソースコードをみることもできます(‘ToolBox classes»フォルダの "zQD"ソースファイルにあります)。 クラス定義は次のようになっているはずです。:
:class POINT super{ object }
record
{ int Y \ Vertical coordinate
int X \ Holizontal coordinate
}
…
;class
すぐ後で全部説明しますが、主要な留意点は、まず第一に、このクラスそれ自体が、 さらに二つのInt(Integer:整数)クラスのインスタンス変数XおよびYを利用しているということです。 これらは、Pointクラスの任意のオブジェクトの内部にあるデータ領域を特定しています。 言い換えると、クラスPointから生成されたどんなオブジェクトも、 データのために確保された小領域(セル)を二つの整数で埋める必要があるということです。 Pointクラスがこのように設計されたのは、 Pointオブジェクト(インスタンス)が生成されるときにはいつも、 点の座標を表現する二つの値は対として扱われると便利だからです。
また、このプログラムを解説する(documenting)一つの方法として、簡単な英語の注をつけ加えることをはじめたことにも注意してください。 Mopsで
コメントを特定する方法は三つあります:
( この種の コメントの 終わりは )
\ これはもうひとつのコメントで、この行の終わりまで続きます
(* この種のコメントは
複数行に渡ることができ、
(* 入れ子にもできます *)
*)
( 、 \ 、 (* および *) はMopsワードであって、空白文字を続けないといけないことに注意してください。 ですから、
(これでは コメントに なりません)
Mopsは"(これでは"をワードとして認識しようとして、 この行の残りをコメントとして扱いません。 あなたは、おそらく、undefined wordエラーメッセージを受けるでしょう。
Pointクラスの残りのステートメントに、少し戻りましょう。 まず、私達は、また、しかし今度はIntクラスの定義を捜さなければなりません。 Pointクラスのデータは、Intクラスの特性をもったインスタンス変数 YおよびXから成っているからです。 クラスIntは、‘Mops ƒ’フォルダの "pStruct"ファイルで定義されています。
:class Int super{ object }
2 bytes data
:m PUT: inline{ ^base w!} ;m
…
;class
クラス Int もMopsの既定義クラスの一つです。 それは、まず
はじめに、そのスーパークラスを、Mopsの多くのクラスがそうですが、Object として宣言しています。 次に、整数オブジェクトが生成される毎に、2バイト(16ビット)データが各値のためにとりおかれるべきことを宣言しています。 三行目は、このクラスのメソッドです(:mで始まり;mで終わります)。 これらのメソッド定義内のメッセージは、ある整数を特別なメモリーエリアに格納します。 (このメソッド定義内の細かいことについては、いまのところ気にしないでください。)
Pointクラスの定義に戻って、九番目のメソッドを見ましょう。:
:m PUT: ( x y -- ) put: Y put: X ;m
これは、XおよびY座標の両方をメモリーに格納するための、Mopsに対する一つの命令です。 ですから、 インスタンス変数(私達の長方形クラスのTopLeftとBotRight)の一つが、X,Y座標にあたる二つの数が与えられれば、 全座標が一つのPUT:メッセージで格納されます。 言い換えれば、Pointオブジェクトに対するPUT:メッセージは、スタック上に二つの数(XとY)を必要とします。
次に、Rectクラスの定義には二つのメソッドがあります。:
:class RECT super{ object }
point TopLeft
point BotRight
:m PUT: ( l t r b -- ) put: BotRight put: TopLeft ;m
:m DRAW: ^base FrameRect ;m
;class
【原文では、スタックコメントが、メソッド定義の同じ行の最後に付いていて、 実際のソースコードでもそうなっていますが、 スタックコメントは、メソッド名と内容コードの間に付けるのが全体としては標準的なようなので、勝手に変えてあります。】
スタック注釈に細かく書いたように、 第一のメソッドPUT:は、オブジェクトがそれを実行する前に、スタック上に四つの整数を必要とします (ここでは文字‘l’, ‘t’, ‘r’, ‘b’で表されています)。 最初の二つの整数(スタック上の上二つ)は、‘put: BotRight’メッセージが、BotRightのクラス -- この場合、クラスPointですが -- に、このPUT:
メソッドの定義をみつけると直ちに、オブジェクトのBotRightで確保された小領域に格納されます。
次の二つの整数は、RectクラスのPUT:メソッド内の‘put: TopLeft’メッセージの結果として、 オブジェクトのTopLeft小領域におかれます。 言い方を変えると、RectクラスのオブジェクトがPUT:セレクターを含むメッセージを受け取ったときには、 そのオブジェクトは、それ自身のクラスの中に対応するメソッド定義を捜します。 そのメソッドが、別のクラスに属するオブジェクトに、自分でメッセージを送ります。 そして、クラスとオブジェクトのつながりの遡行は、純粋にMopsワードで定義されているメソッド(IntクラスのPUT:メソッドのような)に到達するまで続きます。 一連のメッセージによって執られるこれらすべての動作は、メッセージを受けたそのRectオブジェクトのプライベートデータにしか影響を与えません。
私達の長方形クラスの第二のメソッドDRAW:は、FrameRectという名前のカーボンフレームワークルーチン(QuickDrawグラフィックライブラリに属しています)を呼び出して、描画されるべきオブジェクトのデータ領域に現在おかれている座標にしたがって長方形を描きます。 もちろん、そのデータはFrameRectが予定している適切な順序になっていなければなりません。 FrameRectおよび他のCarbonフレームワーク呼出しの大部分は、オブジェクトのデータのアドレスを要求します。 これは、DARW:メソッドにあるワード^BASEで得られます。 そしてこのアドレスは、Carbon
フレームワークコールに渡されるのです。
しかし、私達がこれまでにやった状態のままでは、DRAW:メソッドを呼んでも、うまく動きません。 これは、私達のTopLeftとBotRightの宣言のやり方では、 それらは正規のMopsオブジェクトになってしまうからです。 これが問題な理由は、Mopsオブジェクトは、そのオブジェクトのメモリー内の場所の始まりの部分に、追加的な情報を持っているからです。 この情報は、ある種の事柄をうまくやるためのもので、そのオブジェクトのクラスに関するものを含んでいます。 しかし、Carbonフレームワークは、Mopsオブジェクトについて何も知る必要はありません。 TopLeftとBotRightに当たるそれぞれ2バイト、余分なバイトを含まないデータだけが期待されています。 したがって、この余分な情報を切り落とす方法が必要となりますが、これは‘record{ ... }’を用いることで、次のように行います。:
:class RECT super{ object }
record
{ point TopLeft
point BotRight
}
:m PUT: ( l t r b -- ) put: BotRight put: TopLeft ;m
:m DRAW: ^base FrameRect ;m
;class
注意:‘record{ ... }’という形、または、‘record <構造体名(オプション)>{ ... }’という形のどちらかで利用できます。
レコード(record)の一部として宣言されたインスタンス変数は、どれも追加的な情報を持ちません。 これによって(予想されることですが)、これらの特定インスタンス変数については、行いうることが一定の制限を受けます。 Mopsが追加的情報を利用できなくなるからです。 しかし、後で分かるように、これはあまり深刻な制約にはなりません。
最後に、クラス定義を終わらせるために;class("セミコロン クラス"と読みます)を忘れないで下さい。
最後に一言:クラス定義の形(と、それに関するコード)は、少なくとも一つの空白文字かタブ文字でMopsワードを区切っていれば、 好きなようにすることができます。 私達がこのマニュアルとMopsソースコードで用いたフォーマッティングは、大抵の人にとって、全く読みやすいものだと思いますので、 これと似たようにすることをお勧めします。 空白やコメントをたくさん使うことは、あなたのコードを読んで理解しなければならない他の人の助けになるだろうという意味では、 いつも良い考えであるといえます(この件についてはあなた自身の助けにもなります。何週間かたって、もうそれが記憶に新しいものではなくなってしまったときには。)。 しかし、結局は、あなたの選択次第なのです。
最終更新:2018年12月04日 08:27