設計手順
ほぼ、次のような手順で設計を進めることにします。
めだか飼育システムでは、次のような処理を実現するものとします。 ある日、隣りの家からめだかを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で持っているので必要になります。