第2回 クラスの詳細(4月17日)

今日の課題

■ クラスとインスタンス

オブジェクトは、クラスとインスタンスの2種類がある。クラスは概念であり、インスタンスは具体物である。

たとえば、「クレジットカード」という概念には「カード会社名、カード名義、カード番号、有効期限、…」などの情報が含まれている。一方、自分が持っているクレジットカードは、その具体物であり、これらの情報が具体的に決まっている。

Smalltalk で「クレジットカード」のクラスを定義するとき次の変数が必要であろう(3つに省略している)。

Smalltalk で「クレジットカード」のインスタンスを作るとき、上記の変数にデータを入れておく必要がある。

クレジットカードクラスを作ってみよう

先週の続きの状態ならば、System Browser のカテゴリペインの最下行には「MyObject」というカテゴリが存在している。「MyObject」を左クリックをすると、クラスを定義するための雛形が「編集のペイン」に表示される。

以下の手順で、クラスを定義する。

  1. System Browser を開ける。
  2. 「カテゴリ」のペインで「MyObject」を選択する。
  3. 「編集」のペインで、「#NameOfSubclass」を「#CreditCard」に変更する。
  4. 「編集」のペインで、「instanceVariableNames: ''」を「instanceVariableNames: 'company owner number'」に変更する。
  5. コントロールを押しながら左クリックを押し、「了解(s)」を選択する。

こうして、インスタンス変数として、company, owner, number が定義されるが、インスタンス変数を書き換えるためのメソッド(アクセサと言う)が必要である。メソッドは次の手順で定義する。

  1. System Browser の「クラス」のペインにおいて、CreditCard を選択する。
  2. 「プロトコル」のペインで、「-- all --」を選択する。
  3. 「編集」のペインを全て削除する(通常、緑色でフラッシュしているので、デリートキーなどで一発消去ができる)。
  4. 「メソッド名: 引数名」で定義する。下記の例で、「setCompany:」がメソッド名、「n」が引数名であり、次の行が代入のステートメントで、インスタンス変数 company に n を代入している。
    setCompany: n
      company := n
    
  5. コントロールを押しながら左クリックを押し、「了解(s)」を選択する。

自分のクレジットカード(インスタンス)を作ってみよう

Workspace 上で以下を実行する。

  1. a := CreditCard new ※ 直後に alt キーを押しながら d を押すと実行できる
  2. a setCompany: 'JCB'
  3. a inspect

インスペクトのウインドウが表示され、その中にインスタンス変数の状態が表示されている。

■ 練習

カード名義、カード番号を代入するメソッド setOwner: と setNumber: を作成しよう。

Workspace 上で以下を実行し、インスペクトウインドウの表示の変化を見てみよう。

  1. a setOwner: 'Tokuhisa Masato'
  2. 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機能付きのクレジットカードのクラスを定義するにはどうすれば良いか?

  1. インスタンス変数として、カード会社、カード名義、カード番号、エディ金額を持つクラスを定義する。そして、カード会社、カード名義、カード番号を登録するメソッドを定義した上、エディ金額の参照と変更のためのメソッドを定義する。
  2. クレジットカードクラスの下位クラスとして、エディ金額のインスタンス変数を持つクラスを定義する。そして、エディ金額の参照と変更のためのメソッドを定義する。

どちらが簡単そうに見えますか?

クラスの継承をしてみよう

まず、下位クラス(サブクラス)を定義する。

  1. System Browser を開ける。
  2. 「カテゴリ」のペインで「MyObject」を選択する。
  3. 「編集」のペインで、「Object subclass: #NameOfSubclass」を「CreditCard subclass: #EdyCreditCard」に変更する。
  4. 「編集」のペインで、インスタンス変数の '' を 'edy'に変更する。
  5. 「了解(s)」をする。

次に、エディ金額の参照と変更のためのメソッドを定義する。

  1. 「クラス」のペインで、「EdyCreditCard」を選択する。
  2. 「プロトコル」のペインで、「-- all --」を選択する。
  3. 「編集」のペインで全て削除する。
  4. 「編集」のペインで、メソッド名を「readEdy」とし、ステートメントを「^edy」とする。「^」は C言語の return と同じである。「了解(s)」をすると「↑」になる。
  5. 同様に、メソッド名を「writeEdy:」とし、引数を「n」とする。ステートメントを「edy := n」とする。「了解(s)」をする。

試しに動かしてみよう

Workspace で、以下を実行しよう。

  1. a := EdyCreditCard new
  2. a inspect
  3. a entry: 'VISA' owner: 'Masato Tokuhisa' number: '87654321'
  4. a writeEdy: 10000
  5. 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