総合演習:図書・備品管理システム
本章で学習したJavaのオブジェクト指向機能(ラッパークラス、Generics、抽象クラス、列挙型)を組み合わせて、図書や備品を管理するシステムを設計・実装しましょう。 この演習を通じて、これまでの知識が実際のシステム設計においてどのように組み合わされて機能するかを理解します。
演習
演習1
貸出ステータスを管理する列挙型 LendingStatus、貸出物の共通の性質を定義する抽象クラス LendingItem、および具象クラス Book と Device を作成しなさい。
各クラスは以下の要件を満たすように設計しなさい。
- 列挙型
LendingStatus - 貸出可能な状態を表す
AVAILABLEを定義しなさい。 - 貸出中の状態を表す
LENTを定義しなさい。 メンテナンス中の状態を表す
MAINTENANCEを定義しなさい。抽象クラス
LendingItem- フィールドとして、IDを表す
id(String型)、タイトルを表すtitle(String型)、貸出状態を表すstatus(LendingStatus型)、および貸出中の利用者IDを表すborrowerId(Integer型)を定義しなさい。 borrowerIdは、未貸出の場合は値が存在しない状態を表すために、プリミティブ型ではなくラッパークラスを使用しなさい。- コンストラクタで
idとtitleを受け取り、初期状態としてstatusにLendingStatus.AVAILABLEを、borrowerIdにnullを代入しなさい。 - 外部から各フィールドを参照するためのゲッターメソッド(
getId()、getTitle()、getStatus()、getBorrowerId())およびstatusとborrowerIdを変更するためのセッターメソッド(setStatus(LendingStatus status)、setBorrowerId(Integer borrowerId))を定義しなさい。 貸出物の最大貸出日数を返す抽象メソッド
getMaxLendingDays()を定義しなさい。具象クラス
Book(LendingItemを継承する)- 独自のフィールドとして、著者を表す
author(String型)を定義しなさい。 - コンストラクタで
id、title、authorを受け取り、親クラスのコンストラクタを呼び出すとともに、authorを初期化しなさい。 getMaxLendingDays()をオーバーライドし、書籍の最大貸出日数として14を返しなさい。著者を隠蔽しつつ取得できるように、ゲッターメソッド
getAuthor()を定義しなさい。具象クラス
Device(LendingItemを継承する)- 独自のフィールドとして、保管場所を表す
location(String型)を定義しなさい。 - コンストラクタで
id、title、locationを受け取り、親クラスのコンストラクタを呼び出すとともに、locationを初期化しなさい。 getMaxLendingDays()をオーバーライドし、機器の最大貸出日数として7を返しなさい。- 保管場所を隠蔽しつつ取得できるように、ゲッターメソッド
getLocation()を定義しなさい。
演習2
演習1で作成した貸出物を管理する、Genericsを用いた管理クラス LendingManager<T extends LendingItem> を作成しなさい。
このクラスは以下の要件を満たすように設計しなさい。
- 独自の型パラメータ
Tは、LendingItemまたはそのサブクラスのみを受け付けるように制限しなさい。 - 登録されたアイテムを保持するリストとして
ArrayList<T>を定義しなさい。 - 以下のメソッドを実装しなさい。
- アイテムを登録する
register(T item)メソッド - 指定されたIDのアイテムを貸し出す
lendItem(String id, int borrowerId)メソッド。対象のアイテムが存在し、かつステータスがAVAILABLEである場合のみ、ステータスをLENTに変更し、borrowerIdに引数の値を設定してtrueを返しなさい。それ以外の場合はfalseを返しなさい。 - 指定されたIDのアイテムを返却する
returnItem(String id)メソッド。対象のアイテムが存在し、かつステータスがLENTである場合のみ、ステータスをAVAILABLEに変更し、borrowerIdにnullを設定してtrueを返しなさい。それ以外の場合はfalseを返しなさい。 - 登録されているすべてのアイテムのID、タイトル、ステータス、および貸出中の場合は借用者IDと最大貸出可能日数を表示する
showItems()メソッド
演習3
以下の setup 関数が正しく動作するようにプログラムを構成し、実行結果と同じ出力が得られることを確認しなさい。
また、図書の貸出に失敗するケース(すでに貸し出されている図書の再貸出)を検証するコードを setup 関数へ追加しなさい。
void setup() {
// 書籍を管理するLendingManagerを作成する
LendingManager<Book> bookManager = new LendingManager<Book>();
bookManager.register(new Book("B01", "Javaプログラミング入門", "山田太郎"));
bookManager.register(new Book("B02", "オブジェクト指向設計の基本", "佐藤花子"));
// 機器を管理するLendingManagerを作成する
LendingManager<Device> deviceManager = new LendingManager<Device>();
deviceManager.register(new Device("D01", "開発用ノートPC", "サーバ室"));
deviceManager.register(new Device("D02", "会議用プロジェクター", "201会議室"));
println("--- 初期状態の表示 ---");
bookManager.showItems();
deviceManager.showItems();
println("--- 貸出処理の実行 ---");
// 利用者ID 1001 に図書B01を貸し出す
if (bookManager.lendItem("B01", 1001)) {
println("B01の貸出に成功しました");
} else {
println("B01の貸出に失敗しました");
}
// 利用者ID 2001 に機器D01を貸し出す
if (deviceManager.lendItem("D01", 2001)) {
println("D01の貸出に成功しました");
} else {
println("D01の貸出に失敗しました");
}
println("--- 貸出後の状態表示 ---");
bookManager.showItems();
deviceManager.showItems();
println("--- 返却処理の実行 ---");
if (bookManager.returnItem("B01")) {
println("B01の返却に成功しました");
} else {
println("B01の返却に失敗しました");
}
println("--- 返却後の状態表示 ---");
bookManager.showItems();
}
実行結果
--- 初期状態の表示 ---
ID: B01, タイトル: Javaプログラミング入門, ステータス: AVAILABLE
ID: B02, タイトル: オブジェクト指向設計の基本, ステータス: AVAILABLE
ID: D01, タイトル: 開発用ノートPC, ステータス: AVAILABLE
ID: D02, タイトル: 会議用プロジェクター, ステータス: AVAILABLE
--- 貸出処理の実行 ---
B01の貸出に成功しました
D01の貸出に成功しました
--- 貸出後の状態表示 ---
ID: B01, タイトル: Javaプログラミング入門, ステータス: LENT (利用者ID: 1001), 最大貸出日数: 14日
ID: B02, タイトル: オブジェクト指向設計の基本, ステータス: AVAILABLE
ID: D01, タイトル: 開発用ノートPC, ステータス: LENT (利用者ID: 2001), 最大貸出日数: 7日
ID: D02, タイトル: 会議用プロジェクター, ステータス: AVAILABLE
--- 返却処理の実行 ---
B01の返却に成功しました
--- 返却後の状態表示 ---
ID: B01, タイトル: Javaプログラミング入門, ステータス: AVAILABLE
ID: B02, タイトル: オブジェクト指向設計の基本, ステータス: AVAILABLE
演習の解答例
演習1の解答例
貸出ステータスを定義した列挙型 LendingStatus の実装例は以下の通りである。
状態を表す3つの定数を定義している。
LendingStatus.java
enum LendingStatus {
AVAILABLE,
LENT,
MAINTENANCE
}
貸出物の共通の属性と操作を定義した抽象クラス LendingItem の実装例は以下の通りである。
未貸出の状態を表現するために、利用者IDを保持するフィールドには Integer 型を使用し、初期値として null を代入している。
また、カプセル化の原則に従い、フィールドはすべて private で宣言し、外部からのアクセスにはゲッターとセッターを提供している。
クラス内部のメンバにアクセスする際は this. を明示的に記述している。
LendingItem.java
abstract class LendingItem {
private String id;
private String title;
private LendingStatus status;
private Integer borrowerId;
public LendingItem(String id, String title) {
this.id = id;
this.title = title;
this.status = LendingStatus.AVAILABLE;
this.borrowerId = null;
}
public String getId() {
return this.id;
}
public String getTitle() {
return this.title;
}
public LendingStatus getStatus() {
return this.status;
}
public Integer getBorrowerId() {
return this.borrowerId;
}
public void setStatus(LendingStatus status) {
this.status = status;
}
public void setBorrowerId(Integer borrowerId) {
this.borrowerId = borrowerId;
}
public abstract int getMaxLendingDays();
}
書籍を表現する具象クラス Book の実装例は以下の通りである。
コンストラクタでは super を用いて親クラスのコンストラクタを呼び出している。
getMaxLendingDays メソッドをオーバーライドし、書籍の貸出上限である 14 を返している。
Book.java
class Book extends LendingItem {
private String author;
public Book(String id, String title, String author) {
super(id, title);
this.author = author;
}
public String getAuthor() {
return this.author;
}
@Override
public int getMaxLendingDays() {
return 14;
}
}
機器を表現する具象クラス Device の実装例は以下の通りである。
getMaxLendingDays メソッドをオーバーライドし、機器の貸出上限である 7 を返している。
Device.java
class Device extends LendingItem {
private String location;
public Device(String id, String title, String location) {
super(id, title);
this.location = location;
}
public String getLocation() {
return this.location;
}
@Override
public int getMaxLendingDays() {
return 7;
}
}
演習2の解答例
Genericsを用いて設計した管理クラス LendingManager の実装例は以下の通りである。
型パラメータ T を T extends LendingItem と制限することで、異なる具象クラスに対しても同一の貸出管理ロジックを適用できるようにしている。
貸出および返却処理では、指定されたIDを持つアイテムを走査し、現在のステータスに応じて適切に状態を更新している。
LendingManager.java
import java.util.ArrayList;
class LendingManager<T extends LendingItem> {
private ArrayList<T> items;
public LendingManager() {
this.items = new ArrayList<T>();
}
public void register(T item) {
this.items.add(item);
}
public boolean lendItem(String id, int borrowerId) {
for (T item : this.items) {
if (item.getId().equals(id)) {
if (item.getStatus() == LendingStatus.AVAILABLE) {
item.setStatus(LendingStatus.LENT);
item.setBorrowerId(borrowerId);
return true;
}
}
}
return false;
}
public boolean returnItem(String id) {
for (T item : this.items) {
if (item.getId().equals(id)) {
if (item.getStatus() == LendingStatus.LENT) {
item.setStatus(LendingStatus.AVAILABLE);
item.setBorrowerId(null);
return true;
}
}
}
return false;
}
public void showItems() {
for (T item : this.items) {
if (item.getStatus() == LendingStatus.LENT) {
println("ID: " + item.getId() + ", タイトル: " + item.getTitle() + ", ステータス: " + item.getStatus() + " (利用者ID: " + item.getBorrowerId() + "), 最大貸出日数: " + item.getMaxLendingDays() + "日");
} else {
println("ID: " + item.getId() + ", タイトル: " + item.getTitle() + ", ステータス: " + item.getStatus());
}
}
}
}
演習3の解答例
動作確認および貸出失敗ケースの追加検証を行う setup 関数の実装例は以下の通りである。
すでに貸出中であるアイテムに対して再度 lendItem を呼び出した際に、ステータス判定によって正しく処理が拒否されることを追加検証している。
LendingTest.java
void setup() {
// 書籍を管理するLendingManagerを作成する
LendingManager<Book> bookManager = new LendingManager<Book>();
bookManager.register(new Book("B01", "Javaプログラミング入門", "山田太郎"));
bookManager.register(new Book("B02", "オブジェクト指向設計の基本", "佐藤花子"));
// 機器を管理するLendingManagerを作成する
LendingManager<Device> deviceManager = new LendingManager<Device>();
deviceManager.register(new Device("D01", "開発用ノートPC", "サーバ室"));
deviceManager.register(new Device("D02", "会議用プロジェクター", "201会議室"));
println("--- 初期状態の表示 ---");
bookManager.showItems();
deviceManager.showItems();
println("--- 貸出処理の実行 ---");
// 利用者ID 1001 に図書B01を貸し出す
if (bookManager.lendItem("B01", 1001)) {
println("B01の貸出に成功しました");
} else {
println("B01の貸出に失敗しました");
}
// 利用者ID 2001 に機器D01を貸し出す
if (deviceManager.lendItem("D01", 2001)) {
println("D01の貸出に成功しました");
} else {
println("D01の貸出に失敗しました");
}
println("--- 貸出後の状態表示 ---");
bookManager.showItems();
deviceManager.showItems();
println("--- 返却処理の実行 ---");
if (bookManager.returnItem("B01")) {
println("B01の返却に成功しました");
} else {
println("B01の返却に失敗しました");
}
println("--- 返却後の状態表示 ---");
bookManager.showItems();
println("--- 貸出失敗の検証 ---");
// すでに貸し出されているB01の再貸出を試みる
bookManager.lendItem("B01", 1001); // 貸出中の状態にする
if (bookManager.lendItem("B01", 1002)) {
println("B01の再貸出に成功しました");
} else {
println("B01の再貸出に失敗しました(期待される動作)");
}
}
追加した検証コードを含めた実行結果は以下の通りである。
実行結果
--- 初期状態の表示 ---
ID: B01, タイトル: Javaプログラミング入門, ステータス: AVAILABLE
ID: B02, タイトル: オブジェクト指向設計の基本, ステータス: AVAILABLE
ID: D01, タイトル: 開発用ノートPC, ステータス: AVAILABLE
ID: D02, タイトル: 会議用プロジェクター, ステータス: AVAILABLE
--- 貸出処理の実行 ---
B01の貸出に成功しました
D01の貸出に成功しました
--- 貸出後の状態表示 ---
ID: B01, タイトル: Javaプログラミング入門, ステータス: LENT (利用者ID: 1001), 最大貸出日数: 14日
ID: B02, タイトル: オブジェクト指向設計の基本, ステータス: AVAILABLE
ID: D01, タイトル: 開発用ノートPC, ステータス: LENT (利用者ID: 2001), 最大貸出日数: 7日
ID: D02, タイトル: 会議用プロジェクター, ステータス: AVAILABLE
--- 返却処理の実行 ---
B01の返却に成功しました
--- 返却後の状態表示 ---
ID: B01, タイトル: Javaプログラミング入門, ステータス: AVAILABLE
ID: B02, タイトル: オブジェクト指向設計の基本, ステータス: AVAILABLE
--- 貸出失敗の検証 ---
B01の再貸出に失敗しました(期待される動作)