1学期第6週

テーマ:「めだか飼育システム」の設計


(96.9.20改定)
さて、いよいよJavaでシステムを作ってみましょう。
まず、今週は「めだか飼育システム」の設計です。
とりあえず、現在話題になっている内容の中間報告です。
なんせ、進み方が早すぎて黒板に整理するのが大変です。(^_^;;
少しずつ整備していきますので、間違いなどがあればご指摘下さい。


まとめ
上拾石@名古屋大さんのまとめページ

設計手順

ほぼ、次のような手順で設計を進めることにします。

【0】「めだかの飼育」で何が{できるか/起こるか} を考える。
【1】「めだかの飼育」世界に登場する《物》を考える。… オブジェクト/クラス
【2】《物》の外部への作用や、関係を考える。… メソッド
【3】作用や関係から《物》の情報を考える。… フィールド
【4】必要に応じて新しいオブジェクトを導入し【1】に戻る。

要求仕様
めだか飼育システムでは、次のような処理を実現するものとします。

 ある日、隣りの家からめだかを1匹もらってきました。
  早速、水槽と餌を買って来て、その中に水を入れて、めだかを入れました。
  餌も入れましたが、お腹がすいていないようで食べてくれません。
  次の日に、餌をいれると、元気に食べてくれました。心なしかお腹がふくらんでみえます。
  でも、1匹ではかわいそうなので、今度もう1匹買って入れてあげたいです。

そこで、以下のような要求を満たすシステムを構築することにします。
・メダカが一匹泳いでいる様子を見られる
・メダカが何匹か水槽の中にいる様子を見られる。
 ・メダカにえさをやることができる。
 ・メダカがえさを食べる様子を見られる。


「めだか飼育システム」設計書

最初はめだかは1匹で、水槽で飼われているという超単純バージョンを考える。
超単純に考えると、めだかを飼うだったら
・水槽クラス
・めだかクラス
で可能。
これだと、餌がないからすぐに死んでしまうので、餌をあげれるように
する。ということになると、
・餌クラス
が必要になる。
水クラスについては、 ただ存在するだけで、互いに(有意の){影響/関係}を考え
ないなら、それをわざわざモデル化した世界に持ち込む必要はない。

 基本的なシステムの作成方法として、以下のような考え方ができる。

<めだか>が<水槽>を認識している場合、
 <めだか>---「進めるかな?」---><水槽>
 <水槽>-----「だめ」-----------><めだか>
 <めだか>「別の方向にしよう」

あるいは、

<めだか>が<水槽>を認識しない場合、
 <めだか>「こっちへ行ってみよう」
 <水槽>-----「おいおい、だめだよ」---><めだか>
 <めだか>「痛てて。ぶつかっちゃった。じゃあ、あっちへ行こう」

 現実世界だと、めだかは、目(か他の何かの感覚器官)で行く先を確
認して進むので、前者の方が適切かも。

ところで、「全めだか数」は《めだかの種》に関する値なので、
持つとしたら《めだかクラス》が持つことになる。

一方、「水槽の中の全めだか数」は普遍的な《めだか》ではなく、
そこにある〈水槽インスタンス〉に関する値なので、
持つとしたら《水槽クラス》が持つことになる。

そこで、《めだかクラス》が「全めだか数」を持たない時、「全めだか数」
を調べるには、全ての〈水槽インスタンス〉について「水槽の中のめだか
の数」を問い合わせ、その結果を合計する処理が必要となる。

《めだかクラス》が「全めだか数」を知っているとき、問い合わせには「全
めだか数」を返すだけでいいけど、〈めだか〉の生死によって「全めだか数」
も増減させる、という余分な処理が必要になる。
したがって、〈めだか〉の増減の頻度と、〈水槽〉の数と、「全めだか数」
の問い合わせ頻度によって、どっちが得かが決まることになる。

例えば「世界の何%のめだかが入っているかをリアルタイムに表示する水槽」
なんて物を考えれば「全めだか数」を頻繁に問い合わせがおきるが、単にある
水槽に存在する「全めだか数」を知りたいだけならば、水槽クラスに「全めだ
か数」があれば計算の手間がなく答えることができる。

また、「餌も表示したい」ということになれば餌に位置があることにする。
めだかと餌の位置が重なったら食べたことにすることができる。
餌をあげるのに動機付したいから、めだかに「空腹」という状態を作って、
餌が少な過ぎたらめだかが死ぬことにする。
餌をたくさんやるだけでは面白くないから、多過ぎたら餌が余って水槽が汚
れることにする。
汚れたままだと困るから、水槽を掃除できることにする。
何か目標が欲しいから、めだかは餌を食べると成長することにする。
成長を表現するのに、表示の大きさを変える。
もっと高い目標求めるなら、めだかが成長すると増えることにする。
というような拡張も可能である。

なお、めだかの表示を以下のような文字列を使ってみたい。

 >^)))>< にっこりめだか
 <*)))>< びっくりめだか
 <;)))>< なきむしめだか
 <`)))>< おっとりめだか
 >~)))>< ういんくめだか (^^;



プログラム例:
畑さんの叩き台+馬場さんのチェック+佐藤さんの解説によるJavaによる
プログラム例を示します。
なお、この場合はファイル名はMedakaApplet.javaで保存すること。


import java.awt.*;                      // *1

class Medaka {                          // *2
    Point point;                        // *3

    Medaka(int x, int y) {              // *4
        point = new Point(x, y);        // *5
    }

    public Point getPosition() {        // *6
        return point;
    }
}

public class MedakaApplet extends java.applet.Applet {  // *7
    Medaka medaka;

    public void init() {                // *8
        medaka = new Medaka(100, 100);
    }

    public void paint(Graphics g){      // *9
        Point point = medaka.getPosition();     // *10
        g.drawString("Medaka",point.x,point.y);
    }
}


以下、説明です。

*1 import java.awt.*;
import文と呼ばれる文で、意味しているのは「java.awtパッケージ
に含まれるクラスを単にそのクラス名で参照できるようにする」と
いうことです。これがないと、*3や*6でPointと書いてある所に、
java.awt.Pointと書かなくちゃいけない。*9のGraphicsも同様。
パッケージについては詳しい説明は省きますが、「クラスを目的や
分野などで分類する機構」とでも理解しておいて下さい。
#おまけ
#import java.applet.Applet;
#という記述も付ければ、*7のjava.applet.AppletがAppletで良く
#なる。

*2 class Medaka { ... }
クラスを定義します。クラスの名前はMedaka。{ から対応する } 
までの間にそのクラスのフィールドやメソッドを記述します。
#クラス定義には他に、staticイニシャライザやコンストラクタも
#含まれますが、これらについてはまた後程。

*3 Point point;
フィールドを定義します。この場合、Medakaクラスの各インスタン
スに、Pointクラスのインスタンスを保持するフィールドを定義し
ています。こういうフィールドの事を「インスタンス変数」といい
ます。クラスに保持するフィールドを定義するときには、static宣
言をつけます。例えば、static int numberOfMedaka; のように。
#最後の ; は文の終りを表す区切り記号。文の種類によっては必
#要ない場合もある。

*4 Medaka(int x, int y) { ... }
Medakaクラスのコンストラクタを定義します。コンストラクタはク
ラスと同じ名前のメソッドのように定義します。違いは、返り値の
型(*6を参照)が指定されない事です。
括弧内のint x, int yは仮引数宣言といい、このコンストラクタに
渡される引数がふたつあり、どちらもint型で、順にx, yという値
として使うことを示しています。
#int型はいわゆる整数型です。
#型についてはまた後程。今のところ、オブジェクトの種類(すな
#わちクラス)や、オブジェクトとしては使わない値の種類(整数や
#真偽値など)を指すと思っていて下さい。

*5 point = new Point(x, y);
Pointクラスのコンストラクタを呼び出してインスタンスを生成し、
インスタンス変数pointに格納します。このとき、実引数としてx,
yを渡します。このように、コンストラクタを呼び出してインスタ
ンスを生成する時には、new クラス名(引数, ...) のようにします。

*6 public Point getPosition() { ... }
メソッドを定義します。この例ではメソッドの名前はgetPosition
としています。名前の後ろの括弧()の中には通常、コンストラクタ
と同様に仮引数宣言を書きますが、この場合は特に引数を必要とし
ないので、単に括弧だけ書いてあります。この括弧を省略する事は
できません。
また、Pointというのは、このメソッドを使った時に得られる値(返
り値)の型の指定です。メソッド実行によって何の値も得られない
場合は、型としてvoidを指定します(*8参照)。
publicはこのメソッドをどのクラスからでも使っても良い、という
宣言です。

*7 public class MedakaApplet extends java.applet.Applet { ... }
*2と同様、クラスを定義しています。publicが付いているのは、こ
のクラスをどのクラスからでも使ってよいと宣言するためです。こ
うしておかないと、同じパッケージ内のクラスからしか使えなくなっ
てしまい、ブラウザやアプレットビューワーからクラスを使えなく
なってしまいます。
extends java.applet.Applet という記述は、このクラスが
java.appletパッケージのAppletクラスを元にしたクラスである事
を示しています。元にされるクラスをスーパークラス、スーパーク
ラスを元にして定義したクラスをサブクラスと呼びます。スーパー
クラス、サブクラスの関係についてはここでは詳しく触れませんが、
大まかに分類したのがスーパークラスで、それを更に細かく分類し
たのがサブクラス、とでも覚えておいて下さい。

*8 public void init() { ... }
*6と同様、メソッドを定義していますが、initという名前はアプレッ
トにとって特別な名前なので覚えておきましょう。initメソッドは、
アプレットがブラウザにロードされて、はじめに呼ばれるメソッド
です。この中では、主に情報や部分を生成してフィールドに保持し
ます(いわゆる初期化)。
引数は一つもなく、また返り値もありません。

*9 public void paint(Graphics g) { ... }
これもアプレットにとって特別なメソッドです。
#正確には、java.awt.Componentクラスのサブクラスにとってです
#が、これについては特に触れません。
paintメソッドには、引数としてアプレットを表示すべき描画領域
を示すオブジェクトが渡されますので、ここに自分自身を表示しま
す。

*10 Point point = medaka.getPosition();
こことこの次の行では「メダカの位置を取得し、その位置に
"Medaka"と表示する」ということをしています。まず、メダカの位
置をgetPositionメソッドを使って取得します。取得した位置は変
数pointに保持しています。このpointという変数は、このメソッド
の中でしか使われないので、局所変数、ローカル変数などと呼ばれ
ます。ローカル変数を使う時には、変数定義が必要ですが、この例
では変数の定義と初期化が同時に行われています。この二つを分け
るためには、次のように記述します。
        Point point;
        point = medaka.getPosition();

位置を取得したあとは、引数として渡された描画領域に文字列を表
示します。描画領域はgという仮引数で表されるので、これに対し
てメソッドdrawStringを呼び出しています。引数として渡すのは、
描画する文字列("Medaka")、x座標、y座標です。


上記のプログラムではメダカの色が変わらないので、色を変えられる ように、ソースを書き直しました。(馬場さん) public void init() { medaka = new Medaka(100,100); setBackground(Color.white); //変更部分1 } public void paint( Graphics g ){ Point point = medaka.getPosition(); g.setColor(Color.gray); //変更部分2 g.drawString("Medaka",point.x,point.y); } } 変更部分1: setBackgroundは背景色を指定するメソッドで、通常init() にかきます。この場合は既に規定されている色のうち 白を使っています 変更部分2: setColorはgにこれから描画する色を指定するメソッドです。 この場合は既に規定されている色のうち灰色を使っています。
上記の修正だったらめだかの色は灰色だけになるので、めだか の色をめだかクラスで持つ例を示します。(畑さん) めだかクラスの構成は、 <変数> ・位置 ・めだかの色(追加) <メソッド> ・めだかの位置を教える ・めだがの色を教える(追加) とし、めだかの色の決定方法を 1)ランダムに決める 2)めだかの色を決めれるメソッドを追加する のどちらかにしたいと思います。 <<注意>> ・このまま、このソースをカット&ペーストしても動きません めだかの色の設定方法が決まってないからです。ゆえに、 このソースには引用を付けときます。(^^;;; > import java.awt.*; > > class Medaka { > Point point; > Color color; // 追加部分1 > > Medaka( int x, int y ) { > point = new Point(x,y); > } > > public Point getPosition() { > return point; > } > > public Color getColor() { // 追加部分2 > return color; > } > } > > public class MedakaApplet extends java.applet.Applet { > Medaka medaka; > > public void init() { > medaka = new Medaka(100,100); > setBackground(Color.white); > } > > public void paint( Graphics g ){ > Point point = medaka.getPosition(); > Color color = medaka.getColor(); // plet { !" > > g.setColor( color ); > g.drawString("Medaka",point.x,point.y); > } > } 変更部分1: めだかの色の情報を持っているフィールドです。 変更部分2: めだかの色は何色かを教えてくれるメソッドです。 変更部分3: めだかの色は何色かをめだかクラスから教えてもらえる ように変更しました。
上記のようにするのも有りますし、めだか自身に画面に表示させる事 にして、色や位置は人に教えないって方法もあります。(佐藤さん) import java.awt.*; class Medaka { private Point position; // *1 private Color color = Color.gray; // *A private String figure = ">^)))><"; // *2 Medaka(int x, int y) { //*B position = new Point(x, y); } public void draw(Graphics g) { // *3 g.drawString(figure, position.x, position.y); //*C } } public class MedakaApplet extends java.applet.Applet { Medaka medaka; public void init() { medaka = new Medaka(100, 100); setBackground(Color.white); } public void paint(Graphics g) { medaka.draw(g); } } *1 private Point position; 畑さんのソースではpointとなっていたけど、getPositionとした事 からも分かるように、positionの方がしっくりきますよね。 privateというのは、他のクラスからはこのフィールドは見えない し、触れないよということ。 *2 private String figure = ">^)))><"; めだかの形を変数で持ってみました。形は決めうちにしているけれ ど、これを変更する為のメソッドや、いくつかのタイプを用意して 切替えるなんてことも考えられますね。 *3 public void draw(Graphics g) { ... } めだかが自分自身で画面に表示できるように、メソッドを定義して みました。ここでは今までと同様に文字列を書いていますが、ここ で文字列を書かずにグラフィックで表示するようにすれば、グラフィッ ク版めだかの出来上がり、なんですが、これについてはちょっと保 留:-) 以下、畑さんの説明 *B Medaka(int x, int y) {           position = new Point(x, y); } コンストラクタは、インスタンスが生成される時に実行されるメソッドです。 今回の場合は、Medakaクラスのインスタンスが生成された時点、 medaka = new Medaka(100,100);が実行されたときに実行するメソッド ということですね。主に、初期化とかの処理を行うものです。 で、ここでpositionを決めているのは、とりあえず動かないめだかなので 最初にめだかの位置(水槽内での)を決めようと言う意味でPointクラスの インスタンスであるpositionを生成しているわけです。 後からpositionインスタンスを作っても別にかまわないと思います。 *C g.drawString(figure, position.x, position.y); drawString()は、Graphicsクラスのメソッドです。(APIドキュメント参照) 第1引数:アプレット上に描画するする文字列です。 第2引数:文字列を描焔に描画するする文字列で、 第3引数:文字列を描画する位置(Y座標)です。 Medakaクラスでは、めだかの位置をpositionで持っているので必要になります。

めだかの学校 教室
教員に対するご意見はこちらへ:(Mail to : cbu-staff@ML.psn.or.jp
用務員:臼井義美 (Mail to : yosimi-u@is.aist-nara.ac.jp