Menu
Coddy logo textTech

車両レンタルサービス

CoddyのPHPジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 91/91。

challenge icon

チャレンジ

簡単

車両フリートを管理し、顧客のレンタルを追跡し、車両タイプに基づいて料金を計算する車両レンタルサービスシステムを構築しましょう。この包括的なチャレンジでは、継承、インターフェース、カプセル化、および State パターンを組み合わせて、現実的なレンタルビジネスアプリケーションを作成します。

コードは以下の7つのファイルに分けて構成します:

  • VehicleState.php — 車両が取り得る異なる状態を表す VehicleState インターフェースを定義します。getStatus(): stringcanRent(): bool の2つのメソッドを含めます。次に、このインターフェースを実装する3つのクラスを作成します:
    • AvailableState"Available"true を返します
    • RentedState"Rented"false を返します
    • MaintenanceState"Maintenance"false を返します
  • Vehicle.php — VehicleState ファイルをインクルードします。licensePlate (string, public readonly)、brand (string, public readonly)、および dailyRate (float, protected) のコンストラクタプロモーションを持つ抽象クラス Vehicle を作成します。現在の状態を private プロパティに保存し、デフォルトを AvailableState にします。以下を実装してください:
    • getState(): VehicleState — 現在の状態を返します
    • setState(VehicleState $state): void — 状態を変更します
    • getStatus(): string — 状態の getStatus() に委譲します
    • canRent(): bool — 状態の canRent() に委譲します
    • abstract calculateCost(int $days): float — 車両タイプごとに計算方法が異なります
    • getType(): stringstatic::class を使用してクラス名を返します
  • Vehicles.php — Vehicle ファイルをインクルードします。3つの具体的な車両クラスを作成します:
    • Car — Vehicle を継承します。calculateCost()dailyRate * days を返します
    • Motorcycle — Vehicle を継承します。calculateCost()dailyRate * days * 0.8 (20% 割引) を返します
    • Van — Vehicle を継承し、追加の $mileageRate (float) パラメータを持ちます。calculateCost(int $days, int $miles = 0)(dailyRate * days) + (mileageRate * miles) を返します
  • Customer.phpid (int, public readonly) と name (string, public readonly) のコンストラクタプロモーションを持つ Customer クラスを作成します。レンタル履歴を private 配列で追跡します。以下を実装してください:
    • addRental(string $vehiclePlate, float $cost): void — 履歴に追加します
    • getRentalCount(): int — レンタル回数を返します
    • getTotalSpent(): float — すべてのレンタル費用の合計を返します
  • Rental.php — Vehicle と Customer ファイルをインクルードします。顧客と車両を紐付ける Rental クラスを作成します。customer (Customer, public readonly)、vehicle (Vehicle, public readonly)、days (int, private)、およびオプションで miles (int, private, デフォルト 0) のコンストラクタプロモーションを使用します。以下を実装してください:
    • calculateTotal(): float — 車両が Van の場合は calculateCost() に miles を渡します。それ以外の場合は days のみを渡します
    • complete(): string — 合計を計算し、顧客の履歴に追加し、車両を AvailableState に設定し、"Rental completed: $[total]" (小数点以下2桁) を返します
  • RentalService.php — Rental と Vehicles ファイルをインクルードします。フリートを管理する RentalService クラスを作成します。車両をナンバープレートをキーとする private 配列に保存します。以下を実装してください:
    • addVehicle(Vehicle $vehicle): void — フリートに追加します
    • findVehicle(string $plate): ?Vehicle — 車両または null を返します
    • getAvailableVehicles(): arraycanRent() が true である車両を返します
    • rentVehicle(Customer $customer, string $plate, int $days, int $miles = 0): string|Rental — 車両が見つからない場合は "Vehicle not found" を返し、利用不可の場合は "Vehicle is not available" を返します。それ以外の場合は車両を RentedState に設定し、新しい Rental オブジェクトを返します
    • setMaintenance(string $plate): string — 車両を MaintenanceState に設定します。"[plate] set to maintenance" または "Vehicle not found" を返します
  • main.php — RentalService と Customer ファイルをインクルードします。フリートデータ (JSON)、顧客データ (JSON)、および操作 (JSON) の3つの入力を受け取ります。

    フリート JSON 形式:

    [{"type": "car", "plate": "CAR-001", "brand": "Toyota", "rate": 50}, {"type": "van", "plate": "VAN-001", "brand": "Ford", "rate": 80, "mileage_rate": 0.25}]

    顧客 JSON 形式:

    {"id": 1, "name": "Alice"}

    操作 JSON 形式:

    [{"op": "available"}, {"op": "rent", "plate": "CAR-001", "days": 3}, {"op": "complete", "plate": "CAR-001"}, {"op": "maintenance", "plate": "VAN-001"}, {"op": "customer_stats"}]

    レンタルサービスを作成し、すべての車両をフリートに追加し、顧客を作成します。ナンバープレートごとにアクティブなレンタルを追跡します。各操作を処理してください:

    • "available""Available vehicles: [count]" を出力します
    • "rent" — 車両をレンタルします。成功した場合は Rental を保存し、"Rented [plate] for [days] days" を出力します。Van の場合は操作に含まれる miles を含めます。失敗した場合はエラーメッセージを出力します
    • "complete" — そのプレートの保存されたレンタルを完了させ、結果を出力します
    • "maintenance"setMaintenance() の結果を出力します
    • "status" — 指定されたプレートについて "[plate]: [status]" を出力します
    • "customer_stats""[name]: [count] rentals, $[total] spent" (小数点以下2桁) を出力します

    各結果を新しい行に出力してください。

このシステムは、State パターンが複雑な条件分岐なしに車両の可用性をいかにエレガントに処理するか、継承がいかにポリモーフィックな価格設定を持つクリーンな車両階層を作成するか、そしてカプセル化がいかにワークフロー全体でレンタルデータの整合性を保護するかを示しています。

自分で試してみよう

<?php

require_once 'RentalService.php';
require_once 'Customer.php';

// 入力を読み込む
$fleetJson = trim(fgets(STDIN));
$customerJson = trim(fgets(STDIN));
$operationsJson = trim(fgets(STDIN));

// JSONデータを解析する
$fleetData = (array)json_decode($fleetJson, true);
$customerData = (array)json_decode($customerJson, true);
$operations = (array)json_decode($operationsJson, true);

// TODO: RentalServiceのインスタンスを作成する

// TODO: タイプに基づいてすべての車両をフリートに追加する:
// - "car" -> new Car(plate, brand, rate)
// - "motorcycle" -> new Motorcycle(plate, brand, rate)
// - "van" -> new Van(plate, brand, rate, mileage_rate)

// TODO: customerDataからCustomerインスタンスを作成する

// TODO: ナンバープレートごとにアクティブなレンタルを追跡するための配列を作成する
$activeRentals = [];

// TODO: 各操作を処理し、結果を出力する:
// - "available": "Available vehicles: [count]" を出力する
// - "rent": 車両をレンタルし、成功した場合はRentalを保存し、結果を出力する
// - "complete": レンタルを完了し、結果を出力する
// - "maintenance": メンテナンスを設定し、結果を出力する
// - "status": "[plate]: [status]" を出力する
// - "customer_stats": "[name]: [count] rentals, $[total] spent" を出力する

foreach ($operations as $op) {
    $operation = (array)$op;
    $opType = $operation['op'];
    
    // TODO: 各操作タイプを処理する
    switch ($opType) {
        case 'available':
            // TODO: 利用可能な車両数を出力する
            break;
        case 'rent':
            // TODO: 車両をレンタルし、結果を出力する
            break;
        case 'complete':
            // TODO: レンタルを完了し、結果を出力する
            break;
        case 'maintenance':
            // TODO: メンテナンスを設定し、結果を出力する
            break;
        case 'status':
            // TODO: 車両のステータスを出力する
            break;
        case 'customer_stats':
            // TODO: 顧客の統計情報を出力する
            break;
    }
}

?>

オブジェクト指向プログラミングのすべてのレッスン