車両レンタルサービス
CoddyのJavaジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 87/87。
この最後のチャレンジでは、このコース全体を通して習得したすべてのOOPの概念を統合した、車両レンタルサービスシステムを構築します。
システムは、さまざまな種類の車両フリートをモデル化する必要があります。ナンバープレート、ブランド、1日のレンタル料金などの共通のプロパティを持つ、ベースとなる Vehicle クラスを検討してください。Car、Motorcycle、および Truck などのサブクラスは、座席数、エンジンサイズ、積載量などの特定の属性を使用して、これを拡張できます。
インターフェースはレンタルの動作を定義できます。Rentable インターフェースは、rent()、returnVehicle()、および calculateCost(int days) のようなメソッドを宣言するかもしれません。異なる車両タイプが価格戦略を異なる方法で実装できるため、これはポリモーフィズムの完璧なユースケースになります。
RentalAgency クラスがコンポジションを使用してフリートをどのように管理するかを考えてみましょう。
VehicleNotAvailableException のようなカスタム例外は、エッジケースを処理できます。
車両を作成するためにFactoryパターンを適用したり、車両のカテゴリやレンタル状況にenumを使用したりするかもしれません。
このチャレンジでは、完全で適切に構造化されたOOPシステムを設計する能力が試されます。頑張ってください!
チャレンジ
簡単これまで学んだOOPのすべてを統合した、完全な車両レンタルサービスを構築しましょう!さまざまな種類の車両フリートを管理し、適切なバリデーションを伴うレンタル処理を行い、ファクトリーパターンのように車両を作成するシステムを作成します。
コードは以下の7つのファイルに分けて構成します:
RentalStatus.java: 車両のレンタル状況を表す列挙型(enum)を作成します。AVAILABLE、RENTED、MAINTENANCEの値を含めてください。プライベートフィールドdescription(String)、コンストラクタ、およびゲッターを追加します。AVAILABLEの記述は "ready to rent"、rentedは "currently in use"、maintenanceは "under service" としてください。VehicleNotAvailableException.java:Exceptionを継承したカスタム例外を作成します。コンストラクタはナンバープレート(license plate)を受け取り、Vehicle not available: [licensePlate]という形式のメッセージを作成する必要があります。Rentable.java: レンタル操作を定義するインターフェースを作成します。booleanを返すrent()、booleanを返すreturnVehicle()、およびdoubleを返すcalculateCost(int days)メソッドを宣言します。Vehicle.java: すべての車両の抽象基底クラスを作成します。licensePlate(String)、brand(String)、dailyRate(double)、およびstatus(RentalStatus、AVAILABLEで初期化) のプライベートフィールドを含めます。コンストラクタは licensePlate、brand、dailyRate を引数に取ります。適切なゲッターと status のセッターを含めてください。Stringを返す抽象メソッドgetVehicleType()を宣言します。"[licensePlate] [brand] ([vehicleType]) - $[dailyRate]/day - [status description]"を返すgetDetails()メソッドを実装します。dailyRate は小数点以下2桁でフォーマットしてください。Car.java: Vehicleを継承し、Rentableを実装するクラスを作成します。プライベートフィールドseats(int) を追加します。コンストラクタは licensePlate, brand, dailyRate, seats を引数に取ります。getVehicleType()を実装して"Car"を返すようにします。rent()では、status が AVAILABLE の場合に RENTED に設定して true を返し、それ以外の場合は false を返します。returnVehicle()では、status が RENTED の場合に AVAILABLE に設定して true を返し、それ以外の場合は false を返します。calculateCost(int days)では、dailyRate に days を掛けた値を返します。Motorcycle.java: Vehicleを継承し、Rentableを実装するクラスを作成します。プライベートフィールドengineCC(int) を追加します。コンストラクタは licensePlate, brand, dailyRate, engineCC を引数に取ります。getVehicleType()を実装して"Motorcycle"を返すようにします。Rentableインターフェースのメソッドは Car と同様に実装しますが、calculateCost()では10%の割引を適用(0.9を掛ける)してください。RentalAgency.java: ArrayListを使用して車両フリートを管理するクラスを作成します。以下のメソッドを含めます:addVehicle(Vehicle vehicle)- フリートに車両を追加しますfindVehicle(String licensePlate)- 車両を返すか、見つからない場合は null を返しますrentVehicle(String licensePlate, int days)- 車両を探し、レンタルを試み(Rentableにキャスト)、レンタル費用を返します。車両が見つからない場合、または rent() が false を返した場合はVehicleNotAvailableExceptionをスローしますreturnVehicle(String licensePlate)- 車両を探して返却処理を行い、成功した場合は true を返しますgetAvailableVehicles()- ステータスが AVAILABLE の車両の ArrayList を返します
Main.java: レンタルサービスを構築しましょう!カンマ区切りのコマンド文字列を受け取ります:ADD_CAR:licensePlate:brand:dailyRate:seatsADD_MOTORCYCLE:licensePlate:brand:dailyRate:engineCCRENT:licensePlate:daysRETURN:licensePlateAVAILABLE
各コマンドを処理し、以下を出力してください:
- ADD_CAR/ADD_MOTORCYCLE:
Added: [brand] ([vehicleType]) - RENT:
Rented [licensePlate] for [days] days - Total: $[cost](costは小数点2桁まで)、またはError: [exception message] - RETURN:
Returned: [licensePlate]またはReturn failed: [licensePlate] - AVAILABLE:
Available vehicles:と出力し、その後に利用可能な各車両の詳細を個別の行に出力します
すべてのコマンドの後、
--- Fleet Summary ---と出力し、その後に各車両の詳細を出力します。
入力として、カンマ区切りのコマンド文字列を1つ受け取ります。
例えば、入力が ADD_CAR:ABC-123:Toyota:50.00:5,ADD_MOTORCYCLE:XYZ-789:Honda:40.00:600,RENT:ABC-123:3,RENT:ABC-123:2,AVAILABLE,RETURN:ABC-123 の場合、出力は以下のようになります:
Added: Toyota (Car)
Added: Honda (Motorcycle)
Rented ABC-123 for 3 days - Total: $150.00
Error: Vehicle not available: ABC-123
Available vehicles:
XYZ-789 Honda (Motorcycle) - $40.00/day - ready to rent
Returned: ABC-123
--- Fleet Summary ---
ABC-123 Toyota (Car) - $50.00/day - ready to rent
XYZ-789 Honda (Motorcycle) - $40.00/day - ready to rentこのチャレンジは、継承(Vehicle階層)、インターフェース(Rentable)、列挙型(RentalStatus)、カスタム例外、ポリモーフィズム(異なる車両タイプを統一的に扱う)、およびコンポジション(車両を管理するRentalAgency)を組み合わせて、完全なレンタルサービスシステムを構築するものです!
チートシート
この最終チャレンジでは、複数のOOPコンセプトを組み合わせて完全なシステムを構築します。主なパターンとテクニックは以下の通りです。
フィールドを持つ列挙型(Enums)
列挙型はフィールド、コンストラクタ、およびメソッドを持つことができます:
public enum RentalStatus {
AVAILABLE("ready to rent"),
RENTED("currently in use");
private String description;
private RentalStatus(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}カスタム例外
Exceptionを継承して、ドメイン固有の例外を作成します:
public class VehicleNotAvailableException extends Exception {
public VehicleNotAvailableException(String licensePlate) {
super("Vehicle not available: " + licensePlate);
}
}インターフェースを備えた抽象基底クラス
共通の実装のための抽象クラスと、振る舞いの契約のためのインターフェースを組み合わせます:
public abstract class Vehicle {
private String licensePlate;
private RentalStatus status;
public abstract String getVehicleType();
public String getDetails() {
return licensePlate + " " + getVehicleType() + " - " + status.getDescription();
}
}
public interface Rentable {
boolean rent();
boolean returnVehicle();
double calculateCost(int days);
}
public class Car extends Vehicle implements Rentable {
public boolean rent() {
if (getStatus() == RentalStatus.AVAILABLE) {
setStatus(RentalStatus.RENTED);
return true;
}
return false;
}
}管理のためのコンポジション
オブジェクトのコレクションを管理するためにコンポジションを使用します:
public class RentalAgency {
private ArrayList<Vehicle> fleet = new ArrayList<>();
public void addVehicle(Vehicle vehicle) {
fleet.add(vehicle);
}
public double rentVehicle(String licensePlate, int days) throws VehicleNotAvailableException {
Vehicle vehicle = findVehicle(licensePlate);
if (vehicle == null) {
throw new VehicleNotAvailableException(licensePlate);
}
Rentable rentable = (Rentable) vehicle;
if (!rentable.rent()) {
throw new VehicleNotAvailableException(licensePlate);
}
return rentable.calculateCost(days);
}
}ポリモーフィズムの実践
異なるサブクラスがインターフェースのメソッドを異なる方法で実装できます:
// Car: standard pricing
public double calculateCost(int days) {
return dailyRate * days;
}
// Motorcycle: discounted pricing
public double calculateCost(int days) {
return dailyRate * days * 0.9;
}自分で試してみよう
import java.util.Scanner;
import java.util.ArrayList;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();
// TODO: RentalAgency のインスタンスを作成する
// TODO: 入力をカンマで分割して個別のコマンドを取得する
// TODO: 各コマンドを処理する:
// ADD_CAR:licensePlate:brand:dailyRate:seats -> "Added: [brand] ([vehicleType])" を出力
// ADD_MOTORCYCLE:licensePlate:brand:dailyRate:engineCC -> "Added: [brand] ([vehicleType])" を出力
// RENT:licensePlate:days -> "Rented [licensePlate] for [days] days - Total: $[cost]" (小数点2桁) を出力
// または、例外がスローされた場合は "Error: [exception message]" を出力
// RETURN:licensePlate -> "Returned: [licensePlate]" または "Return failed: [licensePlate]" を出力
// AVAILABLE -> "Available vehicles:" に続いて、利用可能な各車両の詳細を出力
// TODO: すべてのコマンドの後、"--- Fleet Summary ---" を出力
// 続いて各車両の詳細を出力
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
4継承
継承の基本 (extends)super キーワードメソッドのオーバーライド (@Override)コンストラクタチェーンObject クラス単一継承と多段階継承なぜ多重継承ができないのかまとめ:従業員の階層構造