第18週目演習

下準備

テストドライバの導入

第18回目 (秋学期05回目) 分のテストドライバを導入する。以下の手順で行う。

  1. ダウンロード のページを開く (ここをクリック)
  2. プロジェクト「java2007」にある「test」の左側の「+」をクリック
  3. ツリーが展開されるので「install-libraries.xml」を右クリック
  4. 「実行(R)」にマウスカーソルを合わせる
  5. 「1 Ant ビルド」をクリック
  6. 「コンソール」タブに"BUILD SUCCESSFUL"と表示されれば成功
  7. eclipseの画面でプロジェクト「java2007」を右クリック
  8. メニューが表示されるので、「更新」をクリック
  9. "week18.zip" をデスクトップなどにダウンロード
  10. eclipseの画面でプロジェクト「java2007」を右クリック
  11. メニューから「インポート(I)」を選択
  12. 「インポート」ウィンドウが表示されるので、「Zip ファイル」を選択
  13. 「次へ(N)」をクリック
  14. 宛先フォルダー(L): が「java2007」になっていることを確認
  15. From zip file: の右側にある 「参照(R)...」をクリック
  16. ファイルダイアログが表示されるので、ダイアログ内に表示されたダウンロードしたファイルをダブルクリック
  17. 前の画面に戻るので、From zip file: のエリアに正しいパスが入力されていることを確認
  18. フォルダ「/」の左にチェックがついていることを確認 (ついていなければチェックボックスをクリック)
  19. 「警告を出さずに既存リソースを上書き」にチェックがついていることを確認 (上書きしたくないファイルがある場合はチェックを外す)
  20. 「終了 (F)」をクリック

第18週目テストドライバの導入に成功すると、java2007 プロジェクトの test フォルダに j2.lesson05.xml というファイルが作成される。

パッケージの作成

過去の演習を参考にして、「j2.lesson05」というパッケージを作成する。

多角形を表すクラス Polygon の作成

多角形を表すクラス Polygon を j2.lesson05 パッケージ内に作成する。このクラスは、演習の中で親クラスとして利用されるため、以下の内容で必ず作成すること。

package j2.lesson05;

// 多角形を表すクラス
public class Polygon {

    // 空のコンストラクタ
    public Polygon() {
        super();
    }

    // 面積を取得する
    public double area() {
        return 0.0;
    }
    
    // 全ての辺の長さの合計
    public double perimeter() {
        return 0.0;
    }
}

クラスの動きを予想できたら、上記をコピーして対象のソースファイルにペーストすればよい。

なお、2つのメソッドは意味のない値を返しているが、今回はこのクラスを継承し、意味のないメソッドをオーバーライドすることによって、具体的な意味を持った子クラスを作成する。

長方形を表すクラス

長方形を表すクラス Rectangle を作成する。

長方形を表すインスタンスは、次のような特徴を持つことにする。

また、多角形を表すインスタンスは次のような特徴を持っていた。

親クラスの抽出

長方形 is-a 多角形 より、Polygon クラスを継承する。

これにより、次のメンバが Rectangle クラスに引き継がれる。

しかし、上記2つのメソッドは、多角形クラスでは意味のない値を返している。長方形ならではの振る舞いに変更する必要がある。

インスタンスフィールドの抽出

長方形は、次のような情報を持つ。

上記より、インスタンスフィールドを抽出する。なお、カプセル化の考え方に基づいて private で宣言し、二度と変更しないフィールドには final をつける。

コンストラクタの抽出

インスタンスフィールドを初期化するために、次のようなコンストラクタを用意する。

インスタンスメソッドの抽出

長方形を表すインスタンスの特徴より、インスタンスメソッドを抽出する。

オーバーライドするため、メソッドの仕様はできるだけ親クラスで宣言されたものとそろえる。つまり、それぞれのメソッドの仕様は以下のようになる。

骨格の作成

クラスの作成

以下の手順で、パッケージ「j2.lesson05」に「Rectangle」クラスを作成する。

  1. 先ほど作成したパッケージ 「j2.lesson05」の上で右クリック
  2. マウスカーソルを「新規」に合わせる
  3. 「クラス」をクリック
  4. クラス名は Rectangle とする

親クラスの設定

Rectangle に Polygon クラスを継承させる。

クラス名の後に extends Polygon を付け加えればよい。

package j2.lesson05;

public class Rectangle extends Polygon {

}

インスタンスフィールドの作成

Rectangle クラス内にインスタンスフィールドを作成する。

package j2.lesson05;

public class Rectangle extends Polygon {
    private final double width;
    private final double height;
}

コンストラクタの作成

同様に、コンストラクタを用意する。

コンストラクタは戻り値を取らないため、ダミーの return 文を用意する必要はない。フィールドを final で宣言しているため、これらはコンストラクタの引数の値を代入する。

package j2.lesson05;

public class Rectangle extends Polygon {
    private final double width;
    private final double height;

    // コンストラクタ
    public Rectangle(double width, double height) {
        super();
        this.width = width;
        this.height = height;
    }
}

インスタンスメソッドの作成

最後に、インスタンスメソッドを用意する。

ダミーの return 文を追加する。オーバーライド先の親クラスのメソッドを呼び出し、それを返せばよい。例えば、area() メソッドでは return super.area(); とすればよい。

package j2.lesson05;

public class Rectangle extends Polygon {
    private final double width;
    private final double height;

    // コンストラクタ
    public Rectangle(double width, double height) {
        super();
        this.width = width;
        this.height = height;
    }

    // 面積を取得する
    public double area() {
        return super.area();
    }

    // 各辺の長さを取得する
    public double perimeter() {
        return super.perimeter();
    }

    // 文字列に変換する
    public String toString() {
        return super.toString();
    }
}

骨格テスト

ここまでの作業をCtrl+Sを押して保存し、コンパイルを行う (保存時に自動で行われる)。ここでエラーが発生していたら文法エラーなので見直す。

「Rectangleに対する骨格テスト」を実行する。

骨格テストを行った際に緑のバーが表示されれば、外側から見たプログラムの骨格は正しくなっている。

赤いバーが表示された場合、メッセージを元にプログラムを見直すこと。修正を行い、Ctrl+Sで保存した後に「Run」ボタンをクリックする。

メッセージ 詳細
(クラス名), existence j2.lesson05 に対象のクラスが存在していない。パッケージやクラス名を確認
(クラス名), extends <C> クラスが正しいクラスを継承していない (正しくは extends <C>)
(メンバ名), existence 対象の名前を持つメンバが存在していない
(メンバ名), public 対象のメンバが public で宣言されていない
(メンバ名), private 対象のメンバが private で宣言されていない
(メソッド名), not static インスタンスメソッドを作る際に static を指定している
(メソッド名), type <T> メソッドを作る際に戻り値の型を間違えている (正しくは <T>)

各種の実装

今回も擬似コードを書くまでもないので、直接プログラムを書く。

コンストラクタの実装

コンストラクタでは、Rectangle のインスタンスフィールドを設定する。特に骨格と違う点はない。

// コンストラクタ
public Rectangle(double width, double height) {
    super();
    this.height = height;
    this.width = width;
}

area メソッドの実装

area メソッドでは、自分自身の面積を計算して返す。Rectangle は長方形なので、底辺×高さを返せばよい。

// 面積を取得する
public double area() {
    return this.width * this.height;
}

perimeter メソッドの実装

perimeter メソッドでは、各辺の長さの合計を返す。

// 各辺の長さを取得する
public double perimeter() {
    return this.width * 2 + this.height * 2;
}

底辺と高さをそれぞれ2倍することをお忘れなく。

toString メソッドの実装

toString メソッドでは、指定した形式の文字列 ("Rectangle(" + (底辺の長さ) + "," + (高さ) + ")") を返す。

// 文字列に変換する
public String toString() {
    return "Rectangle(" + this.width + "," + this.height + ")";
}

単体テスト

ここまでの作業をCtrl+Sを押して保存し、コンパイルを行う (保存時に自動で行われる)。ここでエラーが発生していたら文法エラーなので見直す。

余裕があれば、JUnitを用いて単体テストを記述せよ。

Rectangle クラス内に mainメソッドを作成し、その中に簡単なテストを記述してみる。例えば、次のような形でよい。

// テストプログラム
public static void main(String[] args) {
    Polygon p = new Rectangle(2.0, 3.0);
    System.out.println(p); // System.out.println(p.toString()); と同じ
    System.out.println("面積:" + p.area());
    System.out.println("各辺の長さ:" + p.perimeter());
}

このプログラムを実行し、各メソッドが期待通りに動いていることを確認する。確認したら、mainメソッドを消しても良い。

「Rectangleに対する単体テスト」を実行する。

単体テストを行った際に緑のバーが表示されれば、各メソッドはある程度正しく作成されていると思われる。

赤いバーが表示された場合、メッセージを元にプログラムを見直すこと。修正を行い、Ctrl+Sで保存した後に「Run」ボタンをクリックする。

メッセージ 詳細
(メソッド名), 期待された結果と異なります (メソッド名)を起動した結果が期待された結果と異なる。下記のテスト項目を参照

テスト項目は以下の通りである。

項目名 詳細
area_3_4 new Rectangle(3.0, 4.0).area()
perimeter_3_4 new Rectangle(3.0, 4.0).perimeter()
toString_3_4 new Rectangle(3.0, 4.0).toString()

プログラム全体

Rectangleクラス全体を掲載しておく。

package j2.lesson05;

public class Rectangle extends Polygon {
    private final double width;
    private final double height;

    // コンストラクタ
    public Rectangle(double width, double height) {
        super();
        this.height = height;
        this.width = width;
    }

    // 面積を取得する
    public double area() {
        return this.width * this.height;
    }

    // 各辺の長さを取得する
    public double perimeter() {
        return this.width * 2 + this.height * 2;
    }

    // 文字列に変換する
    public String toString() {
        return "Rectangle(" + this.width + "," + this.height + ")";
    }
}

三角形を表すクラス

三角形を表すクラス Triangle を作成する。

三角形を表すインスタンスは、次のような特徴を持つことにする。

また、多角形を表すインスタンスは次のような特徴を持っていた。

ほとんどは長方形と同じである。

親クラスの抽出

三角形 is-a 多角形 より、Polygon クラスを継承する。

これにより、次のメンバが Triangle クラスに引き継がれる。

しかし、上記2つのメソッドは、多角形クラスでは意味のない値を返している。三角形ならではの振る舞いに変更する必要がある。

インスタンスフィールドの抽出

三角形は、次のような情報を持つ。

上記より、インスタンスフィールドを抽出する。なお、カプセル化の考え方に基づいて private で宣言し、二度と変更しないフィールドには final をつける。

コンストラクタの抽出

インスタンスフィールドを初期化するために、次のようなコンストラクタを用意する。

インスタンスメソッドの抽出

三角形を表すインスタンスの特徴より、インスタンスメソッドを抽出する。

オーバーライドするため、メソッドの仕様はできるだけ親クラスで宣言されたものとそろえる。つまり、それぞれのメソッドの仕様は以下のようになる。

骨格の作成

ほとんどは先ほど作成した Rectangle と同様である。そのため、説明をいくらか省略する。

クラスの作成

以下の手順で、パッケージ「j2.lesson05」に「Triangle」クラスを作成する。

  1. 先ほど作成したパッケージ 「j2.lesson05」の上で右クリック
  2. マウスカーソルを「新規」に合わせる
  3. 「クラス」をクリック
  4. クラス名は Triangle とする

親クラスの設定

Triangle に Polygon クラスを継承させる。

クラス名の後に extends Polygon を付け加えればよい。

package j2.lesson05;

public class Triangle extends Polygon {

}

インスタンスフィールドの作成

Triangle クラス内にインスタンスフィールドを作成する。

package j2.lesson05;

public class Triangle extends Polygon {
    private final double a;
    private final double b;
    private final double c;
}

コンストラクタの作成

同様に、コンストラクタを用意する。

コンストラクタは戻り値を取らないため、ダミーの return 文を用意する必要はない。

package j2.lesson05;

public class Triangle extends Polygon {
    private final double a;
    private final double b;
    private final double c;

    // コンストラクタ
    public Triangle(double a, double b, double c) {
        super();
        this.a = a;
        this.b = b;
        this.c = c;
    }
}

インスタンスメソッドの作成

最後に、インスタンスメソッドを用意する。

package j2.lesson05;

public class Triangle extends Polygon {
    private final double a;
    private final double b;
    private final double c;

    // コンストラクタ
    public Triangle(double a, double b, double c) {
        super();
        this.a = a;
        this.b = b;
        this.c = c;
    }

    // 面積を取得する
    public double area() {
        return super.area();
    }

    // 各辺の長さを取得する
    public double perimeter() {
        return super.perimeter();
    }

    // 文字列に変換する
    public String toString() {
        return super.toString();
    }
}

骨格テスト

ここまでの作業をCtrl+Sを押して保存し、コンパイルを行う (保存時に自動で行われる)。ここでエラーが発生していたら文法エラーなので見直す。

「Triangleに対する骨格テスト」を実行する。

骨格テストを行った際に緑のバーが表示されれば、外側から見たプログラムの骨格は正しくなっている。

赤いバーが表示された場合、メッセージを元にプログラムを見直すこと。修正を行い、Ctrl+Sで保存した後に「Run」ボタンをクリックする。

メッセージ 詳細
(クラス名), existence j2.lesson05 に対象のクラスが存在していない。パッケージやクラス名を確認
(クラス名), extends <C> クラスが正しいクラスを継承していない (正しくは extends <C>)
(メンバ名), existence 対象の名前を持つメンバが存在していない
(メンバ名), public 対象のメンバが public で宣言されていない
(メンバ名), private 対象のメンバが private で宣言されていない
(メソッド名), not static インスタンスメソッドを作る際に static を指定している
(メソッド名), type <T> メソッドを作る際に戻り値の型を間違えている (正しくは <T>)

各種の実装

今回も擬似コードを書くまでもないので、直接プログラムを書く。

コンストラクタの実装

コンストラクタでは、Triangle のインスタンスフィールドを設定する。特に骨格との違いはない。

// コンストラクタ
public Triangle(double a, double b, double c) {
    super();
    this.a = a;
    this.b = b;
    this.c = c;
}

area メソッドの実装

area メソッドでは、自分自身の面積を計算して返す。Triangle は三角形なので、ヘロンの公式によって面積が求まる。

// 面積を取得する
public double area() {
    double s = (this.a + this.b + this.c) / 2;
    return Math.sqrt(s * (s - this.a) * (s - this.b) * (s - this.c));
}

perimeter メソッドの実装

perimeter メソッドでは、各辺の長さの合計を返す。

// 辺の長さの合計
public double perimeter() {
    return this.a + this.b + this.c;
}

toString メソッドの実装

toString メソッドでは、指定した形式の文字列 ("Triangle(" + (辺aの長さ) + "," + (辺bの長さ) + "," + (辺cの長さ) + ")") を返す。

// 文字列に変換する
public String toString() {
    return "Triangle(" + this.a + "," + this.b + "," + this.c + ")";
}

単体テスト

ここまでの作業をCtrl+Sを押して保存し、コンパイルを行う (保存時に自動で行われる)。ここでエラーが発生していたら文法エラーなので見直す。

余裕があれば、JUnitを用いて単体テストを記述せよ。

Triangle クラス内に mainメソッドを作成し、その中に簡単なテストを記述してみる。例えば、次のような形でよい。

// テストプログラム
public static void main(String[] args) {
    Polygon p = new Triangle(3.0, 4.0, 5.0);
    System.out.println(p);
    System.out.println("面積:" + p.area());
    System.out.println("各辺の長さ:" + p.perimeter());
}

上記のプログラムは、Rectangleクラスのテストプログラムからコンストラクタ呼び出し部分を変更したのみである。どちらもPolygonなので、このようなことが可能となる。このプログラムを実行し、各メソッドが期待通りに動いていることを確認する。確認したら、mainメソッドを消しても良い。

「Triangleに対する単体テスト」を実行する。

単体テストを行った際に緑のバーが表示されれば、各メソッドはある程度正しく作成されていると思われる。

赤いバーが表示された場合、メッセージを元にプログラムを見直すこと。修正を行い、Ctrl+Sで保存した後に「Run」ボタンをクリックする。

メッセージ 詳細
(メソッド名), 期待された結果と異なります (メソッド名)を起動した結果が期待された結果と異なる。下記のテスト項目を参照

テスト項目は以下の通りである。

項目名 詳細
area_3_4_5 new Triangle(3.0, 4.0, 5.0).area()
perimeter_3_4_5 new Triangle(3.0, 4.0. 5.0).perimeter()
toString_3_4_5 new Triangle(3.0, 4.0, 5.0).toString()

プログラム全体

Triangleクラス全体を掲載しておく。

package j2.lesson05;

public class Triangle extends Polygon {

    private final double a;
    private final double b;
    private final double c;

    // コンストラクタ
    public Triangle(double a, double b, double c) {
        super();
        this.a = a;
        this.b = b;
        this.c = c;
    }

    // 面積を取得する
    public double area() {
        double s = (this.a + this.b + this.c) / 2;
        return Math.sqrt(s * (s - this.a) * (s - this.b) * (s - this.c));
    }

    // 辺の長さの合計
    public double perimeter() {
        return this.a + this.b + this.c;
    }

    // 文字列に変換する
    public String toString() {
        return "Triangle(" + this.a + "," + this.b + "," + this.c + ")";
    }
}