Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

خدمة تأجير المركبات

جزء من قسم Object Oriented Programming في رحلة Java على Coddy — الدرس 87 من 87.

لهذا التحدي النهائي، ستقوم ببناء نظام خدمة تأجير المركبات يجمع بين جميع مفاهيم OOP التي أتقنتها طوال هذه الدورة.

يجب أن يقوم نظامك بنمذجة أسطول من أنواع المركبات المختلفة. فكر في فئة Vehicle أساسية بخصائص مشتركة مثل لوحة الترخيص، والعلامة التجارية، ومعدل الإيجار اليومي. يمكن للفئات الفرعية مثل Car و Motorcycle و Truck توسيع ذلك بخصائص محددة مثل عدد المقاعد، أو حجم المحرك، أو سعة الحمولة.

يمكن للواجهات (Interfaces) تعريف سلوكيات التأجير: قد تعلن واجهة Rentable عن دوال (methods) مثل rent() و returnVehicle() و calculateCost(int days). يمكن لأنواع المركبات المختلفة تنفيذ استراتيجيات التسعير بشكل مختلف، مما يجعل هذا حالة استخدام مثالية لتعدد الأشكال (polymorphism).

فكر في كيفية قيام class RentalAgency بإدارة الأسطول باستخدام composition.

استثناءات مخصصة مثل VehicleNotAvailableException يمكنها التعامل مع الحالات الاستثنائية.

قد تقوم بتطبيق نمط المصنع (Factory pattern) لإنشاء المركبات أو استخدام التعدادات (enums) لفئات المركبات وحالة التأجير.

هذا التحدي يختبر قدرتك على تصميم نظام برمجة كائنية التوجه (OOP) متكامل وجيد الهيكلة. بالتوفيق!

challenge icon

التحدي

سهل

لنقم ببناء خدمة تأجير مركبات كاملة تجمع كل ما تعلمته عن البرمجة كائنية التوجه (OOP)! ستقوم بإنشاء نظام يدير أسطولاً من أنواع مختلفة من المركبات، ويتعامل مع عمليات التأجير مع التحقق المناسب من الصحة، ويستخدم نمط المصنع (factory pattern) لإنشاء المركبات.

ستقوم بتنظيم الكود الخاص بك عبر سبعة ملفات:

  • RentalStatus.java: أنشئ enum يمثل حالة تأجير المركبة. قم بتضمين القيم AVAILABLE، و RENTED، و MAINTENANCE. أضف حقلاً خاصاً description (String)، ومنشئاً (constructor)، وطريقة getter. يجب أن يكون للقيم AVAILABLE الوصف "ready to rent"، وللقيمة RENTED الوصف "currently in use"، وللقيمة MAINTENANCE الوصف "under service".
  • VehicleNotAvailableException.java: أنشئ استثناءً مخصصاً يرث من Exception. يجب أن يقبل المنشئ رقم اللوحة (license plate) وينشئ رسالة بالتنسيق Vehicle not available: [licensePlate].
  • Rentable.java: أنشئ واجهة (interface) تحدد عمليات التأجير. صرّح عن الطرق rent() التي تعيد boolean، و returnVehicle() التي تعيد boolean، و calculateCost(int days) التي تعيد double.
  • Vehicle.java: أنشئ فئة أساسية مجردة (abstract base class) لجميع المركبات. قم بتضمين حقول خاصة لـ licensePlate (String)، و brand (String)، و dailyRate (double)، و status (RentalStatus، مهيأة بـ AVAILABLE). يأخذ المنشئ licensePlate و brand و dailyRate. قم بتضمين طرق getter المناسبة وطريقة setter للحالة status. صرّح عن طريقة مجردة getVehicleType() تعيد String. قم بتنفيذ طريقة getDetails() التي تعيد "[licensePlate] [brand] ([vehicleType]) - $[dailyRate]/day - [status description]" مع تنسيق dailyRate إلى منزلتين عشريتين.
  • Car.java: أنشئ فئة ترث من Vehicle وتنفذ Rentable. أضف حقلاً خاصاً seats (int). يأخذ المنشئ licensePlate و brand و dailyRate و seats. قم بتنفيذ getVehicleType() لتعيد "Car". بالنسبة لـ rent()، إذا كانت الحالة AVAILABLE، فقم بتغييرها إلى RENTED وأعد true؛ خلاف ذلك أعد false. بالنسبة لـ returnVehicle()، إذا كانت الحالة 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) - تعيد المركبة (Vehicle) أو null
    • rentVehicle(String licensePlate, int days) - تبحث عن المركبة، وتحاول تأجيرها (بتحويلها إلى Rentable)، وتعيد تكلفة الإيجار. ارمِ VehicleNotAvailableException إذا لم يتم العثور على المركبة أو إذا أعادت rent() القيمة false
    • returnVehicle(String licensePlate) - تبحث عن المركبة وتعيدها، مع إرجاع true عند النجاح
    • getAvailableVehicles() - تعيد ArrayList من المركبات التي حالتها AVAILABLE
  • Main.java: ابنِ خدمة التأجير الخاصة بك! ستتلقى سلسلة نصية من الأوامر مفصولة بفاصلة:
    • ADD_CAR:licensePlate:brand:dailyRate:seats
    • ADD_MOTORCYCLE:licensePlate:brand:dailyRate:engineCC
    • RENT:licensePlate:days
    • RETURN:licensePlate
    • AVAILABLE

    قم بمعالجة كل أمر واطبع ما يلي:

    • ADD_CAR/ADD_MOTORCYCLE: Added: [brand] ([vehicleType])
    • RENT: Rented [licensePlate] for [days] days - Total: $[cost] (التكلفة بمنزلتين عشريتين)، أو Error: [exception message]
    • RETURN: Returned: [licensePlate] أو Return failed: [licensePlate]
    • AVAILABLE: اطبع Available vehicles: متبوعة بتفاصيل كل مركبة متاحة في أسطر منفصلة

    بعد معالجة جميع الأوامر، اطبع --- Fleet Summary --- متبوعة بتفاصيل كل مركبة.

ستتلقى مدخلاً واحداً: سلسلة أوامر مفصولة بفاصلة.

على سبيل المثال، مع المدخل 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)، والـ enums (RentalStatus)، والاستثناءات المخصصة، وتعدد الأشكال (التعامل مع أنواع المركبات المختلفة بشكل موحد)، والتركيب (RentalAgency التي تدير المركبات) في نظام خدمة تأجير كامل!

ورقة مرجعية

يجمع هذا التحدي النهائي بين مفاهيم متعددة للبرمجة كائنية التوجه (OOP) في نظام متكامل. إليك الأنماط والتقنيات الرئيسية:

التعدادات (Enums) مع الحقول

يمكن أن تحتوي التعدادات (Enums) على حقول، ومنشئات (constructors)، ودوال (methods):

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;
    }
}

الاستثناءات المخصصة (Custom Exceptions)

قم بإنشاء استثناءات خاصة بالمجال عن طريق توسيع Exception:

public class VehicleNotAvailableException extends Exception {
    public VehicleNotAvailableException(String licensePlate) {
        super("Vehicle not available: " + licensePlate);
    }
}

الفئات الأساسية المجردة (Abstract Base Classes) مع الواجهات (Interfaces)

اجمع بين الفئات المجردة للتنفيذ المشترك والواجهات لعقود السلوك:

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;
    }
}

التركيب (Composition) للإدارة

استخدم التركيب لإدارة مجموعات من الكائنات:

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);
    }
}

تعدد الأشكال (Polymorphism) في الواقع العملي

يمكن للفئات الفرعية المختلفة تنفيذ دوال الواجهة بشكل مختلف:

// Car: تسعير قياسي
public double calculateCost(int days) {
    return dailyRate * days;
}

// Motorcycle: تسعير مخفض
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: إنشاء مثيل (instance) من 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]" (بمنزلتين عشريتين)
        //                           أو "Error: [exception message]" في حال حدوث استثناء (exception)
        // RETURN:licensePlate -> طباعة "Returned: [licensePlate]" أو "Return failed: [licensePlate]"
        // AVAILABLE -> طباعة "Available vehicles:" متبوعة بتفاصيل كل مركبة متاحة
        
        // TODO: بعد جميع الأوامر، طباعة "--- Fleet Summary ---"
        // متبوعة بتفاصيل كل مركبة
    }
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming