下準備
テストドライバの導入
第19回目 (秋学期06回目) 分のテストドライバを導入する。以下の手順で行う。
- ダウンロード のページを開く (ここをクリック)
- プロジェクト「java2007」にある「test」の左側の「+」をクリック
- ツリーが展開されるので「install-libraries.xml」を右クリック
- 「実行(R)」にマウスカーソルを合わせる
- 「1 Ant ビルド」をクリック
- 「コンソール」タブに"BUILD SUCCESSFUL"と表示されれば成功
- eclipseの画面でプロジェクト「java2007」を右クリック
- メニューが表示されるので、「更新」をクリック
- "week19.zip" をデスクトップなどにダウンロード
- eclipseの画面でプロジェクト「java2007」を右クリック
- メニューから「インポート(I)」を選択
- 「インポート」ウィンドウが表示されるので、「Zip ファイル」を選択
- 「次へ(N)」をクリック
- 宛先フォルダー(L): が「java2007」になっていることを確認
- From zip file: の右側にある 「参照(R)...」をクリック
- ファイルダイアログが表示されるので、ダイアログ内に表示されたダウンロードしたファイルをダブルクリック
- 前の画面に戻るので、From zip file: のエリアに正しいパスが入力されていることを確認
- フォルダ「/」の左にチェックがついていることを確認 (ついていなければチェックボックスをクリック)
- 「警告を出さずに既存リソースを上書き」にチェックがついていることを確認 (上書きしたくないファイルがある場合はチェックを外す)
- 「終了 (F)」をクリック
第19週目テストドライバの導入に成功すると、java2007 プロジェクトの test フォルダに j2.lesson06.xml というファイルが作成される。
パッケージの作成
過去の演習を参考にして、「j2.lesson06」というパッケージを作成する。
レシートを表すインターフェース Receipt の作成
レシートを表すインターフェース Receipt を j2.lesson06 パッケージ内に作成する。このインターフェースは、演習の中で親クラスとして利用されるため、以下の内容で必ず作成すること。
package j2.lesson06;
// レシートを表すインターフェース
public interface Receipt {
// レシート全体の内容を表示する
void show();
// 合計金額を取得する
int getTotalAmount();
}
上記インターフェースは、レシートをカプセル化して扱えるようにするためのもので、
- レシート全体の内容を表示する
- 合計金額を取得する
といった2つの機能を提供する仕様である。
「ほげ書店」のレシートを表すクラス
架空の店「ほげ書店」が発行するレシートを表すクラス HogeBooksReceipt を作成する。
ほげ書店のレシートは次のような形式である。
ほげ書店
TEL:03-xxxx-yyyy
----------------
(購入した商品)
(購入した商品の価格)円
...
合計: (価格の合計)円
----------------
ありがとうございました。
このレシートを表すインスタンスは、次のような特徴を持つことにする。
- "レシート"を実装する
- レシートに購入した商品を追加できる
また、"レシート" インターフェースは、次のようなメソッドを持っていた。
- レシート全体の内容を表示する
- 合計金額を取得する
親クラス、実装するインターフェースの抽出
特徴にある「"レシート"を実装する」より、Receipt インターフェースを実装する。親クラスはデフォルトの Object クラスを継承する。
インスタンスフィールドの抽出
レシートは、次のような情報を持つ。
- レシート has 購入した商品の情報
- 商品名
- 単価
- レシート has-a 購入した商品の合計個数
同じ商品を複数購入した際に、一般的なレシートではその購入個数を表示する。今回はそれほどこだわるつもりはないので、上記のような情報のみを持つことにする。
上記より、インスタンスフィールドを抽出する。なお、カプセル化の考え方に基づいて private で宣言し、二度と変更しないフィールドには final をつける。
- レシート has 購入した商品の情報
- 商品名 -> private final String[] names
- 単価 -> private final int[] costs
- レシート has-a 購入した商品の合計個数 -> private int itemCount
コンストラクタの抽出
インスタンスフィールドを初期化するために、次のようなコンストラクタを用意する。
- public HogeBooksReceipt()
- インスタンスを生成する。
- ここで、names, costs, itemCount をそれぞれ初期化する
インスタンスメソッドの抽出
レシートの骨格の特徴より、インスタンスメソッドを抽出する。
- "レシート"を実装する
- レシートに購入した商品を追加できる -> public void addItem(String name, int cost)
また、"レシート" インターフェースは、次のようなメソッドを持っていたため、それを実装する。
- レシート全体の内容を表示する -> public void show() を実装
- 合計金額を取得する -> public int getTotalAmount() を実装
骨格の作成
クラスの作成
以下の手順で、パッケージ「j2.lesson06」に「HogeBooksReceipt」クラスを作成する。
- 先ほど作成したパッケージ 「j2.lesson06」の上で右クリック
- マウスカーソルを「新規」に合わせる
- 「クラス」をクリック
- クラス名は HogeBooksReceipt とする。
実装するインターフェースの設定
HogeBooksReceipt に Receipt インターフェースを実装させる。
クラス名の後に implements Receipt を付け加えればよい。
package j2.lesson06;
// ほげ書店のレシート
public class HogeBooksReceipt implements Receipt {
}
インスタンスフィールドの作成
HogeBooksReceipt クラス内にインスタンスフィールドを作成する。
package j2.lesson06;
// ほげ書店のレシート
public class HogeBooksReceipt implements Receipt {
private final String[] names;
private final int[] costs;
private int itemCount;
}
コンストラクタの作成
同様に、コンストラクタを用意する。
コンストラクタは戻り値を取らないため、ダミーの return 文を用意する必要はない。いくつかのフィールドを final で宣言しているため、これらはダミーの値として new int[0] を代入しておく。
package j2.lesson06;
// ほげ書店のレシート
public class HogeBooksReceipt implements Receipt {
private final String[] names;
private final int[] costs;
private int itemCount;
// コンストラクタ
public HogeBooksReceipt() {
super();
this.names = new String[0];
this.costs = new int[0];
}
}
インスタンスメソッドの作成
最後に、インスタンスメソッドを用意する。
package j2.lesson06;
// ほげ書店のレシート
public class HogeBooksReceipt implements Receipt {
private final String[] names;
private final int[] costs;
private int itemCount;
// コンストラクタ
public HogeBooksReceipt() {
super();
this.names = new String[0];
this.costs = new int[0];
}
// レシートに商品を追加する
public void addItem(String name, int cost) {
}
// レシートを表示する
public void show() {
}
// 合計金額を取得
public int getTotalAmount() {
return 0;
}
}
骨格テスト
ここまでの作業をCtrl+Sを押して保存し、コンパイルを行う (保存時に自動で行われる)。ここでエラーが発生していたら文法エラーなので見直す。
「HogeBooksReceiptに対する骨格テスト」を実行する。
骨格テストを行った際に緑のバーが表示されれば、外側から見たプログラムの骨格は正しくなっている。
赤いバーが表示された場合、メッセージを元にプログラムを見直すこと。修正を行い、Ctrl+Sで保存した後に「Run」ボタンをクリックする。
| メッセージ | 詳細 |
|---|---|
| (クラス名), existence | j2.lesson06 に対象のクラスが存在していない。パッケージやクラス名を確認 |
| (クラス名), implements <C> | クラスが正しいインターフェースを実装していない (正しくは implements <C>) |
| (メンバ名), existence | 対象の名前を持つメンバが存在していない |
| (メンバ名), public | 対象のメンバが public で宣言されていない |
| (メンバ名), final | 対象のメンバが final で宣言されていない |
| (メンバ名), not static | インスタンスメンバを作る際に static を指定している |
| (メソッド名), not abstract | 対象のメソッドが abstract で宣言されてしまっている |
| (メソッド名), type <T> | メソッドを作る際に戻り値の型を間違えている (正しくは <T>) |
各種の実装
show() の部分だけ擬似コードを書いておく。
レシートを表示する
print "ほげ書店", 改行
print "TEL:03-xxxx-yyyy", 改行
print "----------------", 改行
for i = 0 .. itemCount - 1
print names[i], 改行
print " " + costs[i] + "円", 改行
print 改行
print "合計:" + 合計金額 + "円"
print "----------------", 改行
print "ありがとうございました。", 改行
コンストラクタの実装
コンストラクタでは、HogeBooksReceipt のインスタンスフィールドを設定する。あまりよい実装ではないが、今回は追加できる商品の数を最大5個に限定する。
// コンストラクタ
public HogeBooksReceipt() {
super();
this.names = new String[5];
this.costs = new int[5];
this.itemCount = 0;
}
addItem メソッドの実装
addItem メソッドでは、指定された名前と単価を持つ商品を追加する。配列に随時追加していくのだが、現在どこまで追加したかを覚えておけるように、itemCount というフィールドに、現在までに追加した商品の数を記憶しておく。
// レシートに商品を追加する
public void addItem(String name, int cost) {
this.names[this.itemCount] = name;
this.costs[this.itemCount] = cost;
this.itemCount++;
}
show メソッドの実装
show メソッドでは、擬似コードを元にレシートを表示する。
// レシートを表示する
public void show() {
// print "ほげ書店", 改行
System.out.println("ほげ書店");
// print "TEL:03-xxxx-yyyy", 改行
System.out.println("TEL:03-xxxx-yyyy");
// print "----------------", 改行
System.out.println("----------------");
// for i = 0 .. itemCount - 1
for (int i = 0; i < this.itemCount; i++) {
// print names[i], 改行
System.out.println(this.names[i]);
// print " " + costs[i] + "円", 改行
System.out.println(" " + this.costs[i] + "円");
}
// print 改行
System.out.println();
// print "合計:" + 合計金額 + "円"
System.out.println("合計:" + getTotalAmount() + "円");
// print "----------------", 改行
System.out.println("----------------");
// print "ありがとうございました。", 改行
System.out.println("ありがとうございました。");
}
getTotalAmount メソッドの実装
getTotalAmount メソッドでは、これまでに追加された商品の単価を合計して返す。
// 合計金額を取得
public int getTotalAmount() {
// return total of costs[0..itemCount]
int total = 0;
for (int i = 0; i < this.itemCount; i++) {
total += this.costs[i];
}
return total;
}
単体テスト
ここまでの作業をCtrl+Sを押して保存し、コンパイルを行う (保存時に自動で行われる)。ここでエラーが発生していたら文法エラーなので見直す。
「HogeBooksReceiptに対する単体テスト」を実行する。
単体テストを行った際に緑のバーが表示されれば、各メソッドはある程度正しく作成されていると思われる。
赤いバーが表示された場合、メッセージを元にプログラムを見直すこと。修正を行い、Ctrl+Sで保存した後に「Run」ボタンをクリックする。
| メッセージ | 詳細 |
|---|---|
| (メソッド名), 期待された結果と異なります | (メソッド名)を起動した結果が期待された結果と異なる。下記のテスト項目を参照 |
テスト項目は以下の通りである。
| 項目名 | 詳細 |
|---|---|
| show_void | new HogeBooksReceipt().show() |
| show_sample | new HogeBooksReceipt(), addItem("a", 100), addItem("b", 200), show() |
プログラム全体
HogeBooksReceiptクラス全体を掲載しておく。
package j2.lesson06;
// レシートの骨格
public class HogeBooksReceipt implements Receipt {
private final String[] names;
private final int[] costs;
private int itemCount;
// コンストラクタ
public HogeBooksReceipt() {
super();
this.names = new String[5];
this.costs = new int[5];
this.itemCount = 0;
}
// レシートに商品を追加する
public void addItem(String name, int cost) {
this.names[this.itemCount] = name;
this.costs[this.itemCount] = cost;
this.itemCount++;
}
// レシートを表示する
public void show() {
// print "ほげ書店", 改行
System.out.println("ほげ書店");
// print "TEL:03-xxxx-yyyy", 改行
System.out.println("TEL:03-xxxx-yyyy");
// print "----------------", 改行
System.out.println("----------------");
// for i = 0 .. itemCount - 1
for (int i = 0; i < this.itemCount; i++) {
// print names[i], 改行
System.out.println(this.names[i]);
// print " " + costs[i] + "円", 改行
System.out.println(" " + this.costs[i] + "円");
}
// print 改行
System.out.println();
// print "合計:" + 合計金額 + "円"
System.out.println("合計:" + getTotalAmount() + "円");
// print "----------------", 改行
System.out.println("----------------");
// print "ありがとうございました。", 改行
System.out.println("ありがとうございました。");
}
// 合計金額を取得
public int getTotalAmount() {
// return total of costs[0..itemCount]
int total = 0;
for (int i = 0; i < this.itemCount; i++) {
total += this.costs[i];
}
return total;
}
}
レシートの骨組みを表すクラス
「ほげ書店」のレシートを含め、様々なレシートに書かれている内容を見ると、多くは次のような形をしている。
(レシート上部) (レシート本体) (レシート下部)
- レシート上部 - 店の名前などが書かれている
- レシート本体 - 実際に取引したものや合計金額などが書かれている
- レシート下部 - その他、追加の情報が書かれている
このうち、レシート本体部分は様々なレシートで共通であるが、レシート上部やレシート下部は店ごとに異なる内容が書かれていることが多い。
共通化できる部分がたくさんあるのに、店ごとにレシートを表すクラスを一つ一つ作るのは非常に効率が悪い。そこで今回は、レシートの骨格を形成するクラスとして、AbstractReceipt クラスを作成する。
先ほど作成した「ほげ書店のレシート」も、ここで作成する AbstractReceipt を継承させることによってクラスをすっきりさせる。
レシートの骨格を表すインスタンスは、次のような特徴を持つことにする。
- "レシート"を実装する
- レシート本体を表示できる
- レシートに購入した商品を追加できる
また、"レシート" インターフェースは、次のようなメソッドを持っていた。
- レシート全体の内容を表示する
- 合計金額を取得する
親クラス、実装するインターフェースの抽出
特徴にある「"レシート"を実装する」より、Receipt インターフェースを実装する。親クラスはデフォルトの Object クラスを継承する。
インスタンスフィールドの抽出
インスタンスフィールドは、HogeBooksReceipt と同じものを持つことにする。つまり、次の3つのインスタンスフィールドを持つ。
- private final String[] names
- private final int[] costs
- private int itemCount
コンストラクタの抽出
これも、HogeBooksReceipt と同じものを持つことにする。ただし、コンストラクタの名前はクラスの名前と一致させる必要があるので、そこだけ注意する。
- public AbstractReceipt()
- インスタンスを生成する。
- ここで、names, costs, itemCount をそれぞれ初期化する
インスタンスメソッドの抽出
レシートの骨格の特徴より、インスタンスメソッドを抽出する。
- "レシート"を実装する
- レシート本体を表示できる -> protected void showBody()
- レシートに購入した商品を追加できる -> public void addItem(String name, int cost)
showBody を protected で宣言するのは、show() からのみ呼び出されるからである。
また、"レシート" インターフェースは、次のようなメソッドを持っていたため、それを実装する。
- レシート全体の内容を表示する -> public void show() を実装
- 合計金額を取得する -> public int getTotalAmount() を実装
AbstractReceipt クラスは、一般的なレシートの骨格を表す。骨格の時点では具体的な振る舞いを規定できないものもメソッドとして抽出しておく。
- レシート全体の内容を表示する
- レシート上部を表示する -> protected abstract void showHeader()
- レシート本体を表示する -> protected void showBody() (抽出済み)
- レシート下部を表示する -> protected abstract void showFooter()
骨格の作成
クラスの作成
以下の手順で、パッケージ「j2.lesson06」に「AbstractReceipt」クラスを作成する。
- 先ほど作成したパッケージ 「j2.lesson06」の上で右クリック
- マウスカーソルを「新規」に合わせる
- 「クラス」をクリック
- クラス名は AbstractReceipt とする。
実装するインターフェースの設定
AbstractReceipt に Receipt インターフェースを実装させる。
クラス名の後に implements Receipt を付け加えればよい。
package j2.lesson06;
// レシートの骨格
public abstract class AbstractReceipt implements Receipt {
}
インスタンスフィールドの作成
AbstractReceipt クラス内にインスタンスフィールドを作成する。これは HogeBooksReceipt が持つインスタンスフィールドと同じである。そのため、 HogeBooksReceipt からコピーしてしてくればよい。
package j2.lesson06;
// レシートの骨格
public abstract class AbstractReceipt implements Receipt {
private final String[] names;
private final int[] costs;
private int itemCount;
}
コンストラクタの作成
同様に、コンストラクタを用意する。
コンストラクタの中身は、HogeBooksReceipt のものと同様であるため、この時点で HogeBooksReceipt からコピーしてしてくればよい。
package j2.lesson06;
// レシートの骨格
public abstract class AbstractReceipt implements Receipt {
private final String[] names;
private final int[] costs;
private int itemCount;
// コンストラクタ
public AbstractReceipt() {
super();
this.names = new String[5];
this.costs = new int[5];
this.itemCount = 0;
}
}
インスタンスメソッドの作成
最後に、インスタンスメソッドを用意する。showBody, showHeader, showFooter を除いては、HogeBooksReceipt で使用したものをコピーしてくればよい。
package j2.lesson06;
// レシートの骨格
public abstract class AbstractReceipt implements Receipt {
private final String[] names;
private final int[] costs;
private int itemCount;
// コンストラクタ
public AbstractReceipt() {
super();
this.names = new String[0];
this.costs = new int[0];
}
// レシートに商品を追加する
public void addItem(String name, int cost) {
this.names[this.itemCount] = name;
this.costs[this.itemCount] = cost;
this.itemCount++;
}
// レシートを表示する
public void show() {
// print "ほげ書店", 改行
System.out.println("ほげ書店");
// print "TEL:03-xxxx-yyyy", 改行
System.out.println("TEL:03-xxxx-yyyy");
// print "----------------", 改行
System.out.println("----------------");
// for i = 0 .. itemCount - 1
for (int i = 0; i < this.itemCount; i++) {
// print names[i], 改行
System.out.println(this.names[i]);
// print " " + costs[i] + "円", 改行
System.out.println(" " + this.costs[i] + "円");
}
// print 改行
System.out.println();
// print "合計:" + 合計金額 + "円"
System.out.println("合計:" + getTotalAmount() + "円");
// print "----------------", 改行
System.out.println("----------------");
// print "ありがとうございました。", 改行
System.out.println("ありがとうございました。");
}
// 合計金額を取得
public int getTotalAmount() {
// return total of costs[0..itemCount]
int total = 0;
for (int i = 0; i < this.itemCount; i++) {
total += this.costs[i];
}
return total;
}
// レシートの中身を表示する (共通なのでここで定義する)
protected void showBody() {
}
// レシート上部を表示する (ここでは定義しない)
protected abstract void showHeader();
// レシート下部を表示する (ここでは定義しない)
protected abstract void showFooter();
}
骨格テスト
ここまでの作業をCtrl+Sを押して保存し、コンパイルを行う (保存時に自動で行われる)。ここでエラーが発生していたら文法エラーなので見直す。
「AbstractReceiptに対する骨格テスト」を実行する。
骨格テストを行った際に緑のバーが表示されれば、外側から見たプログラムの骨格は正しくなっている。
赤いバーが表示された場合、メッセージを元にプログラムを見直すこと。修正を行い、Ctrl+Sで保存した後に「Run」ボタンをクリックする。
| メッセージ | 詳細 |
|---|---|
| (クラス名), existence | j2.lesson06 に対象のクラスが存在していない。パッケージやクラス名を確認 |
| (クラス名), implements <C> | クラスが正しいインターフェースを実装していない (正しくは implements <C>) |
| (メンバ名), existence | 対象の名前を持つメンバが存在していない |
| (メンバ名), public | 対象のメンバが public で宣言されていない |
| (メンバ名), protected | 対象のメンバが protected で宣言されていない |
| (メンバ名), final | 対象のメンバが final で宣言されていない |
| (メンバ名), not static | インスタンスメンバを作る際に static を指定している |
| (メソッド名), abstract | 対象のメソッドが abstract で宣言されていない |
| (メソッド名), not abstract | 対象のメソッドが abstract で宣言されてしまっている |
| (メソッド名), type <T> | メソッドを作る際に戻り値の型を間違えている (正しくは <T>) |
骨格実装
骨格を作る際に、HogeBooksReceipt のプログラムをコピーしてきた。今回は「レシートの骨格実装」をするようなクラスを作りたいため、ほげ書店特有の処理をここから削っていく。
show メソッドの書き換え
show() メソッドの中身は、次のようになっていた。
// レシートを表示する
public void show() {
// print "ほげ書店", 改行
System.out.println("ほげ書店");
// print "TEL:03-xxxx-yyyy", 改行
System.out.println("TEL:03-xxxx-yyyy");
// print "----------------", 改行
System.out.println("----------------");
// for i = 0 .. itemCount - 1
for (int i = 0; i < this.itemCount; i++) {
// print names[i], 改行
System.out.println(this.names[i]);
// print " " + costs[i] + "円", 改行
System.out.println(" " + this.costs[i] + "円");
}
// print 改行
System.out.println();
// print "合計:" + 合計金額 + "円"
System.out.println("合計:" + getTotalAmount() + "円");
// print "----------------", 改行
System.out.println("----------------");
// print "ありがとうございました。", 改行
System.out.println("ありがとうございました。");
}
それに対して、レシートの一般的な形は次のようなものである。
(レシート上部) (レシート本体) (レシート下部)
この show() メソッドを3つの部分に分ける。すると、次のように書ける。
// レシートを表示する
public void show() {
// show レシート上部
showHeader();
// show レシートの中身
showBody();
// show レシート下部
showFooter();
}
showBody メソッドの実装
showBody() メソッドには、HogeBooksReceipt の show() メソッドにある命令のうち、レシート本体部分だけを持ってくる。
// レシートの中身を表示する (共通なのでここで定義する)
protected void showBody() {
// for i = 0 .. itemCount - 1
for (int i = 0; i < this.itemCount; i++) {
// print names[i], 改行
System.out.println(this.names[i]);
// print " " + costs[i] + "円", 改行
System.out.println(" " + this.costs[i] + "円");
}
// print 改行
System.out.println();
// print "合計:" + 合計金額 + "円"
System.out.println("合計:" + getTotalAmount() + "円");
}
これ以外の「レシート上部」「レシート下部」に属する表示部分は、レシートごとに特有なのでここでは定義しない。
単体テスト
ここまでの作業をCtrl+Sを押して保存し、コンパイルを行う (保存時に自動で行われる)。ここでエラーが発生していたら文法エラーなので見直す。
「AbstractReceiptに対する単体テスト」を実行する。
単体テストを行った際に緑のバーが表示されれば、各メソッドはある程度正しく作成されていると思われる。
赤いバーが表示された場合、メッセージを元にプログラムを見直すこと。修正を行い、Ctrl+Sで保存した後に「Run」ボタンをクリックする。
| メッセージ | 詳細 |
|---|---|
| (メソッド名), 期待された結果と異なります | (メソッド名)を起動した結果が期待された結果と異なる。下記のテスト項目を参照 |
テスト項目は以下の通りである。
| 項目名 | 詳細 |
|---|---|
| show_void | new MockReceipt().show() |
| show_sample | new MockReceipt(), addItem("a", 100), addItem("b", 200), show() |
abstract で指定したクラスは、インスタンス化できないためテストを行いにくい。ここでの単体テストは、AbstractReceipt クラスを継承した簡単なクラス (MockReceipt) を用意して、そのインスタンスに対してテストを行う。
プログラム全体
AbstractReceiptクラス全体を掲載しておく。
package j2.lesson06;
// レシートの骨格
public abstract class AbstractReceipt implements Receipt {
private final String[] names;
private final int[] costs;
private int itemCount;
// コンストラクタ
public AbstractReceipt() {
super();
this.names = new String[5];
this.costs = new int[5];
this.itemCount = 0;
}
// レシートに商品を追加する
public void addItem(String name, int cost) {
this.names[this.itemCount] = name;
this.costs[this.itemCount] = cost;
this.itemCount++;
}
// レシートを表示する
public void show() {
// show レシート上部
showHeader();
// show レシートの中身
showBody();
// show レシート下部
showFooter();
}
// 合計金額を取得
public int getTotalAmount() {
// return total of costs[0..itemCount]
int total = 0;
for (int i = 0; i < this.itemCount; i++) {
total += this.costs[i];
}
return total;
}
// レシートの中身を表示する (共通なのでここで定義する)
protected void showBody() {
// for i = 0 .. itemCount - 1
for (int i = 0; i < this.itemCount; i++) {
// print names[i], 改行
System.out.println(this.names[i]);
// print " " + costs[i] + "円", 改行
System.out.println(" " + this.costs[i] + "円");
}
// print 改行
System.out.println();
// print "合計:" + 合計金額 + "円"
System.out.println("合計:" + getTotalAmount() + "円");
}
// レシート上部を表示する (ここでは定義しない)
protected abstract void showHeader();
// レシート下部を表示する (ここでは定義しない)
protected abstract void showFooter();
}
AbstractReceipt を継承した HogeBooksReceipt
架空の店「ほげ書店」が発行するレシートを表すクラス HogeBooksReceipt を AbstractReceipt を継承したものに書き換える。
ほげ書店のレシートは次のような形式である。
ほげ書店 TEL:03-xxxx-yyyy ---------------- (showBody()と同じ出力) ---------------- ありがとうございました。
つまり、レシート上部の出力内容は以下の通りで、
ほげ書店 TEL:03-xxxx-yyyy ----------------
レシート下部の出力内容は以下の通りである。
---------------- ありがとうございました。
先ほど作成した AbstractReceipt を継承すれば、レシート上部と下部の表示だけを変更することによって「ほげ書店」のレシートを作成することができる。
変更する点
親クラスの変更
HogeBooksReceipt に AbstractReceipt クラスを継承させる。
クラス名の後に extends AbstractReceipt を付け加えればよい。AbstractReceipt は Receipt インターフェースを実装していたので、これによって間接的に HogeBooksReceipt も Receipt を実装することになる。
package j2.lesson06;
public class HogeBooksReceipt extends AbstractReceipt {
// ...
}
コンストラクタの書き換え
HogeBooksReceipt クラス内のコンストラクタでは特にすべきことがなくなった。親クラスのコンストラクタを呼び出すのみである。
package j2.lesson06;
// ほげ書店のレシート
public class HogeBooksReceipt extends AbstractReceipt {
// コンストラクタ
public HogeBooksReceipt() {
super();
}
// ...
}
インスタンスメソッドの書き換え
インスタンスメソッドを書き換える。レシートとしての機能のほとんどは AbstractReceipt クラスが持っているため、HogeBooksReceipt 特有の部分だけを書くことにする。
HogeBooksReceipt 特有の部分はレシートの上部と下部の表示だけで、それぞれ showHeader, showFooter をオーバーライドすることによって実現できる。
package j2.lesson06;
// ほげ書店のレシート
public class HogeBooksReceipt extends AbstractReceipt {
// コンストラクタ
public HogeBooksReceipt() {
super();
}
// レシート上部を表示する
protected void showHeader() {
// print "ほげ書店", 改行
System.out.println("ほげ書店");
// print "TEL:03-xxxx-yyyy", 改行
System.out.println("TEL:03-xxxx-yyyy");
// print "----------------", 改行
System.out.println("----------------");
}
// レシート下部を表示する
protected void showFooter() {
// print "----------------", 改行
System.out.println("----------------");
// print "ありがとうございました。", 改行
System.out.println("ありがとうございました。");
}
}
上記以外のメソッドは必要ない。
単体テスト
ここまでの作業をCtrl+Sを押して保存し、コンパイルを行う (保存時に自動で行われる)。ここでエラーが発生していたら文法エラーなので見直す。
「HogeBooksReceiptに対する単体テスト」 を再度実行し、変更によって挙動が変化していないことを確認せよ。
プログラム全体
変更した HogeBooksReceipt クラス全体を掲載しておく。
package j2.lesson06;
// ほげ書店のレシート
public class HogeBooksReceipt extends AbstractReceipt {
// コンストラクタ
public HogeBooksReceipt() {
super();
// 特にすべきことはない
}
// レシート上部を表示する
protected void showHeader() {
System.out.println("ほげ書店");
System.out.println("TEL:03-xxxx-yyyy");
System.out.println("----------------");
}
// レシート下部を表示する
protected void showFooter() {
System.out.println("----------------");
System.out.println("ありがとうございました。");
}
}