段階的にプログラミングすることとオブジェクトの性質を抽出することは同じではないが、OOPにおいてはオブジェクトの所有する本質的な部分を最初に実装し、その後に修飾部分を実装するのが一般的なプログラミングスタイルである。
Javaを使って、このカウンタ問題を解きながら、現在のJavaの環境と開発スタイルについて整理してみる。
これから作成する「カウンタ」をポンチ絵で表したのが、下図である。(1)
(2)図から明らかなようにカウンタへの操作としては、
がある。また、モデルが保持しなければならない情報としては、
が必要であることが分かる。
このようなカウンタが持つべき概念的な属性や機能はアブストラクト・クラスとして定義し、アブストラクト・クラスの機能を実装した節[整数カウンタクラス(IntegerCount)]をサブクラスとする。AbstractCountクラスはアブトラクト・クラスであるためインスタンスを生成するメソッドを持っていない、increment, decrement, toStringは下位クラスで実装が必要なアブストラクトメソッドであるため、右側に○にAのアイコンが表示されている。IngegerCountがAbstractCountのコンクリートクラス(コンストラクタを持つクラス)であり、ここでincrement, decrementメソッドを定義している。
整数カウンタのUMLのクラス構成を図[整数カウンタクラス構成]に示す。(4)
最初にカウンタモデルと外界とのインタフェースについて、記述する。(5)カウンタのインタフェースは、前節のincrement, decrement, resetの3個である。これをjavaで記述すると以下のようになる。
package model; /** * カウンタのインタフェース * * @author Hiroshi TAKEMOTO * @version $Id$ */ public interface ICount { /** * カウンタを加算する(1カウントアップ) */ public void increment(); /** * カウンタを減算する(1カウントダウン) */ public void decrement(); /** * カウンタをリセットする */ public void reset(); }
インタフェースの次にカウントクラスの概念クラスを作成する。概念クラスには、カウンタが持つべき共通の機能や属性を定義する。概念クラスの特徴としては、コンストラクタを持たないこと、実装がない概念メソッドが存在することがあげられる。カウンタのアブストラクトカウントクラスをリスト[AbstractCount.java]に示す。
package model; /** * カウントのアブストラクトクラス * * @author Hiroshi TAKEMOTO * @version $Id$ */ public abstract class AbstaractCount implements ICount { /** * 現在の値 */ private Object value_ = null; /** * リセット値 */ private Object resetValue_ = null; /** * @see model.ICount#increment() */ public abstract void increment(); /** * @see model.ICount#decrement() */ public abstract void decrement(); /** * @see java.lang.Object#toString() */ public abstract String toString(); /** * @see model.ICount#reset() */ public void reset() { setValue_(getResetValue_()); } /** * Returns the resetValue_. * @return Object */ protected Object getResetValue_() { return resetValue_; } /** * Returns the value_. * @return Object */ public Object getValue_() { return value_; } /** * Sets the resetValue_. * @param resetValue_ The resetValue_ to set */ protected void setResetValue_(Object resetValue_) { this.resetValue_ = resetValue_; } /** * Sets the value_. * @param value_ The value_ to set */ public void setValue_(Object value_) { this.value_ = value_; } }
次に、整数カウンタクラスを作成する。オブジェクトを値とするために、整数値を内部データ型(int)ではなく、Integerクラスを使ってラップしたために、加算と減算の手続きに少し無理がある。
value_ = new Integer(((Integer)value_).intValue() + 1);
のようにvluae_を(Integer)にキャストして、intValue()でその整数値を取得し、1足した後、その値を整数値として持つIntergerオブジェクトを生成してvalue_にセットしている。また、リセット値は0と使用した。
package model; /** * 整数カウンタ * * @author Hiroshi TAKEMOTO * @version $Id: IntegerCount.java,v 1.1.1.1 2003/11/14 20:23:36 take Exp $ */ public class IntegerCount extends AbstaractCount { /** * 整数カウンタのコンストラクタ */ public IntegerCount() { setResetValue_(new Integer(0)); reset(); } /** * @see model.ICount#increment() */ public void increment() { setValue_(new Integer(((Integer)getValue_()).intValue() + 1)); } /** * @see model.ICount#decrement() */ public void decrement() { setValue_(new Integer(((Integer)getValue_()).intValue() - 1)); } /** * @see java.lang.Object#toString() */ public String toString(){ return (((Integer)getValue_()).toString()); } }
実際にEclipseを使って整数カウンタを作成してみる。Eclipseのインストール等環境開発の手順については、下記を参照されたい。また、整数カウンタの作成手順をMicrosoft Mutlimedia Playerのムービーとして作成したので、movies/mov_2.5_1.wmvからダウンロードして参照されたい。
Eclipseのアイコンをダブルクリックして、Eclipseを起動する。Eclipseでは、「プロジェクト」という単位でプログラムを管理しているため、まず最初にプロジェクトを作成する必要がある。以下の手順でプロジェクトを作成する。
最初にICountインタフェースを作成する。(7)
public void increment(); public void decrement(); public void reset();
次にカウンタの概念クラスであるAbstractCountを作成する。
public abstract void increment(); public abstract void decrement();
public void reset() { setValue_(getResetValue_()); }
private Object value_ = null; private Object resetValue_ = null;
最後に、整数カウンタのIntegerCountクラスを作成する。
引数のないコンストラクタAbstractCount()を以下のように定義する
public IntegerCount() { setResetValue_(new Integer(0)); reset(); }
ムービーの例の様に、“this”をsetResetValue_の前に置いて、this.とタイプすると入力候補が表示される。この入力補完機能は、CTRL-スペースを押すことによっても表示することができる。
public String toString(){ return (((Integer)getValue_()).toString()); }
これで、整数カウンタが完成した。
Eclipseには、「スクラックブック」という機能があり、メイン関数を作らなくてもjavaの断片を評価し実行する機能がある。これを使ってIntegerCountの動作を確認する。
以下の手順でスクラップブックを作成し、IntegerCountの動作を確認する。
ICount c = new IntegerCount(); // IntegerCountのインスタンスcを生成する c.increment(); // cのカウントを1増やす System.out.println(c); // cの現在の値を表示する c.increment(); // cのカウントを1増やす System.out.println(c); // cの現在の値を表示する c.increment(); // cのカウントを1増やす System.out.println(c); // cの現在の値を表示する c.decrement(); // cのカウントを1減らす System.out.println(c); // cの現在の値を表示する c.reset(); // cをリセットする System.out.println(c); // cの現在の値を表示する
1 2 3 2 0
Pnutsは、javaのプログラムのテストを簡単に行うために開発されたインタプリタ形式の言語処理系である。(10)そのため、スクラップブックのようにjava言語ではなく、タイプ宣言のない、独自の言語仕様を使って記述するため、記述量も少なく、テストが短時間で行うことができる。スクラップブックは、評価を一括して行うのに対し、pnutsを使ったテストは、実行結果を保持しながら逐次的に処理を実行し、確認することができるのが大きな特徴である。pnutsのインストールについては、下記を参照されたい。Eclipseでpnutsを使用する場合の設定方法は、次の通りである。
Pnutsを使ったテストの作成手順をMicrosoft Mutlimedia Playerのムービーとして作成したので、movies/mov_2.6_1.wmvからダウンロードして参照されたい。
import("model.*") // モデルのインポート宣言 count = IntegerCount() // IntegerCountの生成 count.increment() // 1カウントアップ println(count) // 現在の値を出力 count.increment() count.increment() count.increment() println(count)
Eclipseは、Junitに対応しているため、Junitを使った単体テストをEclipseの環境内で実行することができる。特に、エラーが発見されたときに、その実行結果をクリックすると失敗したメソッドが表示されるので、デバッグが非常に楽になる。(12)JUnitのテストプログラムをパッケージに含めるか否かについては議論があるが、私はmodelと同じレベル“unittest”というパッケージにJunitのテストケースを入れることにしている。このようにすることで、プロジェクトの単体テストがスムーズに行え、出荷時に不要ならばunittestを削除するだけで済む。以下にEclipseを使ったJunitのテストケースの追加方法を示す。
// incrementoのテスト // decrementのテスト // resetのテスト // reset後のincrementのテスト // reset後のdecrementのテスト // increment, decrement, resetの混合
package unittest; import model.IntegerCount; import junit.framework.TestCase; /** * 問題領域でのモデルの単体テスト * @author Hiroshi TAKEMOTO * * @version $Id$ */ public class PDC_ModelTestCase extends TestCase { private IntegerCount count = null; public void setUp() { count = new IntegerCount(); } public void tearDown() { count = null; } // 生成されたインスタンスの値をチェック public void testConstractor() { // 初期値が0であることを確認する this.assertEquals(count.getValue_(), new Integer(0)); } // incrementoのテスト public void testIncrement() { // 生成、直後のincrement count.increment(); this.assertEquals(count.getValue_(), new Integer(1)); // incrementを複数繰り返した後の値 count.increment(); count.increment(); count.increment(); this.assertEquals(count.getValue_(), new Integer(4)); } // decrementのテスト public void testDecrement() { // 生成、直後のdecrement count.decrement(); this.assertEquals(count.getValue_(), new Integer(-1)); // decrementを複数繰り返した後の値 count.decrement(); count.decrement(); count.decrement(); this.assertEquals(count.getValue_(), new Integer(-4)); } // resetのテスト public void testReset() { // 生成、直後のreset count.reset(); this.assertEquals(count.getValue_(), new Integer(0)); // incrementを複数繰り返した後のreset count.increment(); count.increment(); count.increment(); count.reset(); this.assertEquals(count.getValue_(), new Integer(0)); } // reset後のincrementのテスト public void testIncrementAfterReset() { count.increment(); count.increment(); count.increment(); this.assertEquals(count.getValue_(), new Integer(3)); count.reset(); this.assertEquals(count.getValue_(), new Integer(0)); count.increment(); this.assertEquals(count.getValue_(), new Integer(1)); } // reset後のdecrementのテスト public void testDecrementAfterReset() { count.decrement(); count.decrement(); count.decrement(); this.assertEquals(count.getValue_(), new Integer(-3)); count.reset(); this.assertEquals(count.getValue_(), new Integer(0)); count.decrement(); this.assertEquals(count.getValue_(), new Integer(-1)); } // increment, decrement, resetの混合 public void testMixedOperation() { count.increment(); count.increment(); count.decrement(); this.assertEquals(count.getValue_(), new Integer(1)); count.reset(); this.assertEquals(count.getValue_(), new Integer(0)); count.decrement(); count.decrement(); count.increment(); this.assertEquals(count.getValue_(), new Integer(-1)); } }
これで、整数カウンタの作成とテストが完了したので、整数カウンタを拡張してみる。
リセット値が異なるカウンタを作成したいニーズがでてくる。このような場合に、便利なのがインスタンスを生成するコンストラクタを複数用意する事である。整数カウンタにリセット値指定のコンストラクタを以下のように定義する。
/** * 初期値(リセット値)指定の整数カウンタのコンストラクタ */ public IntegerCount(int initial) { setResetValue_(new Integer(initial)); reset(); }
次に16進数で表示するカウンタを考えてみる。整数カウンタとの違いは、表示方法だけなのでtoStringメソッドのみを修正することになる。従ってIntegerCountのサブクラスとして、HexCountクラスを作成する。
package model; /** * 16進カウンタ * * @author Hiroshi TAKEMOTO * * @version $Id$ */ public class HexCount extends IntegerCount { /** * @see java.lang.Object#toString() */ public String toString() { // 16進表示は、0xA0のように最初に0xをつけた return ("0x" + Integer.toString(((Integer)this.getValue_()).intValue(), 16).toUpperCase()); } }
作成したHexCountを整数カウンタと同様にテストしてみる。スクラップブックのPDCTest.jpageをコンストラクタ部分を以下のように変更して、
ICount c = new HexCount(); // HexCountのインスタンスcを生成する
実行すると次のように出力される。
0x1 0x2 0x3 0x2 0x0
カウンタの最後の例題としてカレンダカウンタを実装する。まず、仕様を決める。
java APIからそれらしいクラスを検索してみる。それらしいDateクラスが見つかったので、スクラップブックで動作を調べてみる。
Date d = new Date(); System.out.println(d)
と入力し、インポートにjava.util.dateを追加し、評価すると
Wed Dec 03 17:04:46 JST 2003
と表示される。しかし、Dateクラスでは、日付のカウントができない。Calendarクラスには、addメソッドが用意されおり、日付の増減が可能であり、今回の目的に合致する。Calendarから日付を取り出すには、getTimeメソッドを使うことにする。試しにbshで動作を確認してみる。Java APIの説明からカット&ペーストするだけでAPIの動作を確認することができるのもスクラップブックの利点である。
Calendar now = Calendar.getInstance(); // Calendarのインスタンス生成を変数nowにセット System.out.println(now.getTime()); now.add(Calendar.DATE, 1); // add()で日付を1日増やす System.out.println(now.getTime());
を評価すると、
Wed Dec 03 17:11:34 JST 2003 Thu Dec 04 17:11:34 JST 2003
が得られる。よって求めるカレンダカウンタは、次のようになる。
package model; import java.util.Calendar; import java.util.Date; /** * カレンダカウンタ * * @author Hiroshi TAKEMOTO * * @version $Id$ */ public class CalendarCount extends AbstaractCount { private Calendar now_; public CalendarCount() { now_ = Calendar.getInstance(); // Calendarのインスタンスを生成する setResetValue_(now_.getTime()); // 現在時刻をリセット値にセットする reset(); } /** * @see model.ICount#increment() */ public void increment() { now_.add(Calendar.DATE, 1); // 日付を1増やす setValue_(now_.getTime()); } /** * @see model.ICount#decrement() */ public void decrement() { now_.add(Calendar.DATE, -1); // 日付を1減らす setValue_(now_.getTime()); } /** * @see java.lang.Object#toString() */ public String toString() { return (((Date)getValue_()).toString()); } }
HexCountと同様にスクラップブックで、
ICount c = new CalendarCount(); // CalendarCountのインスタンスcを生成する
テストすると、
Thu Dec 04 17:49:05 JST 2003 Fri Dec 05 17:49:05 JST 2003 Sat Dec 06 17:49:05 JST 2003 Fri Dec 05 17:49:05 JST 2003 Wed Dec 03 17:49:05 JST 2003
問題領域でのカウントの例題にGUIを追加する前に、pnutsを使ってGUIを追加する方法を紹介する。値とボタンを含むフレームを作ってみる。
このフレームを表示するpnutsスクリプトは、次のようになる。
import("javax.swing.*") import("model.*") m = IntegerCount() f = JFrame("GUI Model Test") p = JPanel() l = JTextField(5) b = JButton("increment") p.add(l) p.add(b) f.getContentPane().add(p) f.pack() f.show() function callback(e) { m.increment() l.setText(m.getValue_().toString()) } bind(b, "actionPerformed", callback)
スクリプトの実行は、「実行」メニューからPnutsToolを選択し、Pnutsの「ファイル」メニューから「読み込む」を選択しpnut_gui.pnutsを読みと図[pnutsを使ったGUI作成]が表示する。Pnutsウィンドウの上部パネルに
m.increment() m.increment() l.setText(m.getValue_().toString()) m.reset() println(m)
と入力すると、
の様に、GUIを表示しながら、pnutsのスクリプトパネル(上部パネル)で" m.reset() "のように動的にコマンドを実行しながら、オブジェクトを変更することができる。これがインタプリタの大きな特徴である。
以上のように、javaのインタプリタを使うことによってテストに要する時間が短縮され、テスト方法の自由度も増すことが検証できた。
問題領域の段階的プログラミングとして、ここでは
ことを検証できた。問題領域で作成したクラスの構成をUMLで記述する。
作成したプログラムの構造や技術的なポイントを後ですぐに取り戻すことができるように整理することが大切である。(13)