第2回 クラスの詳細(4月17日)
今日の課題
■ クラスとインスタンス
オブジェクトは、クラスとインスタンスの2種類がある。クラスは概念であり、インスタンスは具体物である。
たとえば、「クレジットカード」という概念には「カード会社名、カード名義、カード番号、有効期限、…」などの情報が含まれている。一方、自分が持っているクレジットカードは、その具体物であり、これらの情報が具体的に決まっている。
Smalltalk で「クレジットカード」のクラスを定義するとき次の変数が必要であろう(3つに省略している)。
- company: カード会社名
- owner: カード名義
- number: カード番号
Smalltalk で「クレジットカード」のインスタンスを作るとき、上記の変数にデータを入れておく必要がある。
- company = JCB
- owner = 徳久雅人
- number = 12345678
クレジットカードクラスを作ってみよう
先週の続きの状態ならば、System Browser のカテゴリペインの最下行には「MyObject」というカテゴリが存在している。「MyObject」を左クリックをすると、クラスを定義するための雛形が「編集のペイン」に表示される。
以下の手順で、クラスを定義する。
- System Browser を開ける。
- 「カテゴリ」のペインで「MyObject」を選択する。
- 「編集」のペインで、「#NameOfSubclass」を「#CreditCard」に変更する。
- 「編集」のペインで、「instanceVariableNames: ''」を「instanceVariableNames: 'company owner number'」に変更する。
- コントロールを押しながら左クリックを押し、「了解(s)」を選択する。
こうして、インスタンス変数として、company, owner, number が定義されるが、インスタンス変数を書き換えるためのメソッド(アクセサと言う)が必要である。メソッドは次の手順で定義する。
- System Browser の「クラス」のペインにおいて、CreditCard を選択する。
- 「プロトコル」のペインで、「-- all --」を選択する。
- 「編集」のペインを全て削除する(通常、緑色でフラッシュしているので、デリートキーなどで一発消去ができる)。
- 「メソッド名: 引数名」で定義する。下記の例で、「setCompany:」がメソッド名、「n」が引数名であり、次の行が代入のステートメントで、インスタンス変数 company に n を代入している。
setCompany: n
company := n
- コントロールを押しながら左クリックを押し、「了解(s)」を選択する。
自分のクレジットカード(インスタンス)を作ってみよう
Workspace 上で以下を実行する。
- a := CreditCard new ※ 直後に alt キーを押しながら d を押すと実行できる
- a setCompany: 'JCB'
- a inspect
インスペクトのウインドウが表示され、その中にインスタンス変数の状態が表示されている。
■ 練習
カード名義、カード番号を代入するメソッド setOwner: と setNumber: を作成しよう。
Workspace 上で以下を実行し、インスペクトウインドウの表示の変化を見てみよう。
- a setOwner: 'Tokuhisa Masato'
- a setNumber: '12345678'
(発展)
3つの情報は基本的な情報なので、同時に登録したい。3引数のメソッドを定義しよう。
entry: c owner: o number: n
company := c.
owner := o.
number := n
複数のステートメントは、ピリオドで区切りながら入力する。
(実行例)
x := CreditCard new
x entry: 'AMEX' owner: 'Tokuhisa Masato' number: '43215678'
■ クラスの継承
最近のクレジットカードには、電子マネー(Edy)の機能や、ショッピングポイントの機能が付いている。これらのカードは、「クレジットカード」+αというように、概念的に少し違いがある。
Edy機能付きのクレジットカードのクラスを定義するにはどうすれば良いか?
- インスタンス変数として、カード会社、カード名義、カード番号、エディ金額を持つクラスを定義する。そして、カード会社、カード名義、カード番号を登録するメソッドを定義した上、エディ金額の参照と変更のためのメソッドを定義する。
- クレジットカードクラスの下位クラスとして、エディ金額のインスタンス変数を持つクラスを定義する。そして、エディ金額の参照と変更のためのメソッドを定義する。
どちらが簡単そうに見えますか?
クラスの継承をしてみよう
まず、下位クラス(サブクラス)を定義する。
- System Browser を開ける。
- 「カテゴリ」のペインで「MyObject」を選択する。
- 「編集」のペインで、「Object subclass: #NameOfSubclass」を「CreditCard subclass: #EdyCreditCard」に変更する。
- 「編集」のペインで、インスタンス変数の '' を 'edy'に変更する。
- 「了解(s)」をする。
次に、エディ金額の参照と変更のためのメソッドを定義する。
- 「クラス」のペインで、「EdyCreditCard」を選択する。
- 「プロトコル」のペインで、「-- all --」を選択する。
- 「編集」のペインで全て削除する。
- 「編集」のペインで、メソッド名を「readEdy」とし、ステートメントを「^edy」とする。「^」は C言語の return と同じである。「了解(s)」をすると「↑」になる。
- 同様に、メソッド名を「writeEdy:」とし、引数を「n」とする。ステートメントを「edy := n」とする。「了解(s)」をする。
試しに動かしてみよう
Workspace で、以下を実行しよう。
- a := EdyCreditCard new
- a inspect
- a entry: 'VISA' owner: 'Masato Tokuhisa' number: '87654321'
- a writeEdy: 10000
- Transcript show: a readEdy
■ 小レポート
身の回りの物を Smalltalk のクラスとサブクラスで定義しよう。
- ヒント:
- 基本機能のものを上位のクラスに、発展機能のものを下位クラスで定義するとよいでしょう。たとえば、「携帯電話(KeitaiDenwa)」をクラスにすると、インスタンス変数は「電話番号」であり、メソッドとして「電話番号表示(showMyNumber)」、「電話をかける(callTo:)」、「電話を受ける(receive)」が考えられる(メソッドの処理は、Transcriptに「呼出し中」や「通話中」と表示する程度で良い)。次に、「お財布携帯(OsaifuKeitai)」をサブクラスにすると、「携帯電話」クラスに追加するインスタンス変数は「クレジット番号」、「使用許可フラグ」であり、追加するメソッドは「ロック(lock)」、「ロック解除(unlock)」、「支払い(pay:)」が考えられる。
- 提出の要領:
- プログラムと実行の様子が分かるように画面に表示させる(プログラムは、上位クラス、下位クラス、作成した全メソッド)。
- 画面のハードコピーをプリンタで印刷する。
- プログラムにコメントを記入する。
継承の様子がわかる実行の様子を、特に強調して説明をする。
来週、演習の開始時に集めます。
画面の取り込みは、Linux の画面の一番上のメニューバーの「アクション」を選択し、「スクリーンショット」を選択する。すると、Screenshot.png というファイルが作られる。その印刷は、「gimp」などのグラフィックツールを使用して印刷する。
■ 伝達事項
小レポートの採点後は、いったん返却します。返却されたレポートは各自がファイルに綴じておいて下さい。最終レポートと同時にそのファイルを提出してもらいます。その後、ファイルは返却しませんので、最後の提出時は必要ならば自分でコピーをとっておいて下さい。
■ おまけ
変数の考え方
変数の考え方には2つがある。
注意:「箱モデル」と「紐モデル」という言葉は徳久が勝手に作った言葉なので、試験などでは使えません。
箱モデルは、C言語でいうと「int x=1,y=2;」や「char c;」の宣言により使用できる変数である。箱モデルでは、値の形と、箱の形が合うときに、代入することができる。
紐モデルは、変数に箱が対応するのではなく、変数に紐がぶらさがっている。紐モデルでは、代入する際、紐の始点をほどいて2つに分けて、もう一方の変数に持ってもらう。
もともと x の持っていた紐は、消えてしまう。上の星に継がっている紐が全て消えると、誰も上の星にメッセージを送ることができなくなる。寂しい…。
紐には種類は無いの?
Smalltalk では、紐に種類分けが無い。
C言語でも紐モデルがある。ポインタ変数である。でも、C言語では、紐に種類分けがある。たとえば「int *p; char *q;」では、p の紐は int の箱につながっており、q の紐は char の箱につながっている。だから、紐の先のデータをどのように処理すれば良いのかが分かる(int だったら四則演算ができるなど)。
注意:C言語で「int *p;」という宣言のみでは紐の先に箱は無い。「int i, *p; p = &i;」とすると、p の紐の先に i の箱が有ることになる。
紐を使って処理ができるの?
Smalltalkでは、紐の先のオブジェクトが、int なのか char なのか、何者なのか分からないのに、どうして処理ができるのか? それは、オブジェクトがメソッドを持っているからだ。
(C) 2008.4.14 by tokuhisa