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