خدمة تأجير المركبات
جزء من قسم 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) متكامل وجيد الهيكلة. بالتوفيق!
التحدي
سهللنقم ببناء خدمة تأجير مركبات كاملة تجمع كل ما تعلمته عن البرمجة كائنية التوجه (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) أو nullrentVehicle(String licensePlate, int days)- تبحث عن المركبة، وتحاول تأجيرها (بتحويلها إلى Rentable)، وتعيد تكلفة الإيجار. ارمِVehicleNotAvailableExceptionإذا لم يتم العثور على المركبة أو إذا أعادت rent() القيمة falsereturnVehicle(String licensePlate)- تبحث عن المركبة وتعيدها، مع إرجاع true عند النجاحgetAvailableVehicles()- تعيد ArrayList من المركبات التي حالتها AVAILABLE
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](التكلفة بمنزلتين عشريتين)، أو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 ---"
// متبوعة بتفاصيل كل مركبة
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP
الملفات الخارجيةمقدمة في الـ OOPالفئات (Classes) مقابل الكائنات (Objects)الكلمة المفتاحية thisالدوال (Methods)الحقول (Attributes)دالة البناء (Constructor)تعدد أشكال دالة البناء (Constructor Overloading)مراجعة - آلة حاسبة بسيطة4الوراثة
الوراثة الأساسية (extends)الكلمة المفتاحية superإعادة تعريف الدوال (@Override)تسلسل المنشئات (Constructor Chaining)كلاس Objectالوراثة الأحادية ومتعددة المستوياتلماذا لا توجد وراثة متعددة للكلاساتمراجعة - هيكلية الموظفين7الدوال الخاصة وفئة Object
دالة toString()equals() و hashCode()دالة clone()compareTo() و Comparableواجهة Comparatorمراجعة - الترتيب المخصص2محددات الوصول والتغليف
نظرة عامة على مستويات الوصولدوال Getter و Setterإخفاء البياناتالكلمة المفتاحية finalمراجعة - مدير الحساب البنكي5تعدد الأشكال (Polymorphism)
أساسيات الـ Method Overloadingالـ Method Overriding (في وقت التشغيل)الـ Upcasting والـ Downcastingمعامل الـ instanceofالفئات والدوال المجردة (Abstract)مراجعة - حاسبة الأشكال8مفاهيم OOP المتقدمة
الـ Composition مقابل الـ Inheritanceالـ Aggregation مقابل الـ Compositionالفئات الداخلية والمتداخلة والمجهولة (Inner, Nested & Anonymous)الـ Enums ودوالهاالـ Records (Java 16+)الـ Sealed Classes (Java 17+)11أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Builderنمط Observerنمط Strategy3خصائص الفئة والعضو الساكن
المتغيرات الخاصة بالكائن مقابل المتغيرات الساكنةالدوال الساكنةالكتل البرمجية الساكنةالثوابت (static final)ملخص - العداد والأدوات المساعدة6الواجهات (Interfaces) والأصناف المجردة (Abstract Classes)
مقدمة في الواجهات (Interfaces)تنفيذ الواجهات (Interfaces)تنفيذ واجهات متعددةالـ Default و Static في الواجهاتالأصناف المجردة مقابل الواجهاتالواجهات الوظيفية (Functional Interfaces)مراجعة - نظام الدفع9الأنواع العامة (Generics)
مقدمة في الأنواع العامةالأصناف العامة (Generic Classes)الدوال العامة (Generic Methods)معاملات الأنواع المقيدةالرموز البديلة (?, extends, super)مراجعة - الحاويات العامة12أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المكرر