الـ Aggregation مقابل الـ Composition
جزء من قسم البرمجة كائنية التوجه في رحلة Java على Coddy. الدرس 48 من 87.
كلٌّ من التجميع والتركيب شكلٌ من أشكال علاقات «يمتلك»، لكنهما يختلفان في جانب جوهري واحد: الملكية والاعتماد على دورة الحياة.
في التركيب، لا يمكن للكائن المحتوى أن يوجد مستقلًا عن الحاوية. عندما تُدمَّر الحاوية، تُدمَّر أجزاؤها أيضًا. فكّر في House وكائنات Room الخاصة بها: لا توجد الغرف دون المنزل:
class House {
private Room[] rooms;
public House(int numRooms) {
rooms = new Room[numRooms];
for (int i = 0; i < numRooms; i++) {
rooms[i] = new Room(); // ينشئ House الغرف ويمتلكها
}
}
}في التجميع، يمكن للكائن المحتوى أن يوجد بشكل مستقل. يستخدم الحاوي الكائن لكنه لا يتحكم في دورة حياته. فكّر في Team وكائنات Player الخاصة به: يمكن للاعبين أن يوجدوا حتى من دون فريق:
class Team {
private List<Player> players;
public Team() {
players = new ArrayList<>();
}
public void addPlayer(Player player) {
players.add(player); // يستخدم Team اللاعبين الموجودين
}
}التمييز الأساسي: في التركيب، تنشئ الحاوية أجزائها داخليًا. أما في التجميع، فتُمرَّر الكائنات من الخارج. عند حذف Team، تظل كائنات Player موجودة. وعند حذف House، تختفي كائنات Room التابعة لها أيضًا.
التحدي
سهللننشئ نظامًا للمقررات الجامعية يوضّح كلًا من التجميع والتركيب. سترى كيف يمتلك Course كائنات Lecture الخاصة به (تركيب)، بينما لا يفعل سوى الإشارة إلى كائنات Professor الموجودة بصورة مستقلة (تجميع).
ستوزّع شيفرتك على أربعة ملفات:
Professor.java: أنشئ صنفًا يمثّل أستاذًا يمكنه الوجود بصورة مستقلة عن أي مقرر. لدى Professor حقلان خاصان:name(String) وdepartment(String). أضف مُنشئًا لتهيئة الحقلين، ودوالًا للحصول على قيمة كل منهما. أعد تعريفtoString()ليُرجع:Prof. [name] ([department])Lecture.java: أنشئ صنفًا يمثّل محاضرة لا يكون لها معنى إلا ضمن مقرر. لدى Lecture حقلان خاصان:topic(String) وdurationMinutes(int). أضف مُنشئًا ودوالًا للحصول على القيم. أعد تعريفtoString()ليُرجع:Lecture: [topic] ([durationMinutes] min)Course.java: هنا ستوضّح كلتا العلاقتين! لدى Course ثلاثة حقول خاصة:title(String)، وprofessor(Professor - تجميع)، وlectures(مصفوفة من كائنات Lecture - تركيب).يستقبل المُنشئ عنوان المقرر، وكائن Professor (يُمرّر من الخارج - تجميع)، وعدد المحاضرات. أنشئ مصفوفة Lecture داخل المُنشئ واملأها بكائنات Lecture جديدة. لكل محاضرة عند الفهرس
i، أنشئها بالموضوع"Topic " + (i + 1)والمدة45دقيقة. يوضّح هذا الإنشاء الداخلي مفهوم التركيب.أضف أيضًا دالة
getInfo()تُرجع سلسلة نصية متعددة الأسطر:Course: [title] Instructor: [professor.toString()] Lectures: [number of lectures]أضف كذلك دالة
listLectures()تُرجع سلسلة نصية تتضمن قيمة toString() لكل محاضرة في سطر مستقل.Main.java: اجمع كل شيء معًا! ستتلقى أربع مدخلات: اسم الأستاذ (String)، وقسم الأستاذ (String)، وعنوان المقرر (String)، وعدد المحاضرات (int).أولًا، أنشئ Professor بالاسم والقسم المُعطيين. هذا الأستاذ موجود بصورة مستقلة. ثم أنشئ Course، ومرّر إليه العنوان والأستاذ (تجميع) وعدد المحاضرات (سينشئ المقرر محاضراته داخليًا - تركيب).
اطبع نتيجة
getInfo()، ثم اطبع سطرًا فارغًا، ثم اطبع نتيجةlistLectures().
ستتلقى أربع مدخلات بالترتيب: اسم الأستاذ، والقسم، وعنوان المقرر، وعدد المحاضرات.
لاحظ الفرق الأساسي: يُنشأ Professor من الخارج ويُمرّر إلى Course (التجميع: يمكن للأستاذ تدريس مقررات أخرى أو الوجود من دون هذا المقرر). أما Lectures فتُنشأ داخل مُنشئ Course (التركيب). فهي تنتمي حصريًا إلى هذا المقرر ولن تكون موجودة من دونه).
جرّب بنفسك
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// اقرأ المدخلات
String professorName = scanner.nextLine();
String department = scanner.nextLine();
String courseTitle = scanner.nextLine();
int numberOfLectures = scanner.nextInt();
// TODO: أنشئ كائنًا من Professor (يوجد بشكل مستقل - تجميع)
// TODO: أنشئ كائنًا من Course، مع تمرير الأستاذ إليه (تجميع)
// ستنشىء الدورة محاضراتها الخاصة داخليًا (تركيب)
// TODO: اطبع نتيجة getInfo()
// TODO: اطبع سطرًا فارغًا
// TODO: اطبع نتيجة listLectures()
}
}يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
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أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المكررتدرّب بنفسك: مترجم Java عبر الإنترنت