الـ Aggregation مقابل الـ Composition
جزء من قسم البرمجة كائنية التوجه في رحلة Java على Coddy. الدرس 48 من 87.
كلٌّ من aggregation و composition هما شكلان من علاقات "has-a"، لكنهما يختلفان في جانب حاسم واحد: الملكية والاعتماد على دورة الحياة.
في composition، لا يمكن للكائن المحتوى أن يوجد independently عن الحاوية. عند تدمير الحاوية، تُدمَّر أجزاؤها أيضًا. فكّر في كائنات 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` ينشئ ويمتلك `rooms`
}
}
}في aggregation، يمكن للكائن المحتوى أن يوجد بشكل مستقل. تستخدم الحاوية الكائن، لكنها لا تتحكم في دورة حياته. تأمل Team وكائنات Player التابعة له: يوجد اللاعبون حتى من دون فريق:
class Team {
private List<Player> players;
public Team() {
players = new ArrayList<>();
}
public void addPlayer(Player player) {
players.add(player); // يستخدم Team اللاعبين الموجودين
}
}التمييز الأساسي: في composition، ينشئ الحاوي أجزائه داخليًا. في aggregation، تُمرَّر الكائنات من الخارج. عند حذف Team، تظل كائنات Player موجودة. عند حذف House، تختفي كائنات Room الخاصة بـ its أيضًا.
التحدي
سهللنبنِ نظامًا للمقررات الجامعية يوضح كلاً من aggregation وcomposition. سترى كيف يمتلك Course كائنات Lecture الخاصة به (composition)، بينما يكتفي بالإشارة إلى كائنات Professor التي توجد independently (aggregation).
ستنظّم شيفرتك عبر أربعة ملفات:
Professor.java: أنشئ class تمثّل أستاذًا يمكنه أن يوجد independently عن أي مقرر. لدى Professor حقلان private:name(String) وdepartment(String). أضف constructor لتهيئة الحقلين، وgetter methods لكل منهما. أعد تعريفtoString()بحيث تُرجع:Prof. [name] ([department])Lecture.java: أنشئ class تمثّل محاضرة لا يكون لها معنى إلا ضمن مقرر. لدى Lecture حقلان private:topic(String) وdurationMinutes(int). أضف constructor وgetters. أعد تعريفtoString()بحيث تُرجع:Lecture: [topic] ([durationMinutes] min)Course.java: هنا ستوضح كلتا العلاقتين! لدى Course ثلاثة حقول private:title(String)، وprofessor(Professor - aggregation)، وlectures(array من كائنات Lecture - composition).يأخذ constructor عنوان المقرر، وكائن Professor (يمرَّر من الخارج - aggregation)، وعدد المحاضرات. داخل constructor، أنشئ array الخاصة بـ Lecture واملأها بكائنات Lecture جديدة. لكل محاضرة عند الفهرس
i، أنشئها بموضوع"Topic " + (i + 1)وduration مقدارها45دقيقة. يوضح هذا الإنشاء الداخلي composition.أضف method باسم
getInfo()تُرجع String متعددة الأسطر:Course: [title] Instructor: [professor.toString()] Lectures: [number of lectures]أضف أيضًا method باسم
listLectures()تُرجع String تحتوي على استدعاء toString() لكل محاضرة في سطر مستقل.Main.java: اجمع كل شيء معًا! ستتلقى أربعة inputs: اسم الأستاذ (String)، وقسم الأستاذ (String)، وعنوان المقرر (String)، وعدد المحاضرات (int).أولًا، أنشئ Professor بالاسم والقسم المحددين. يوجد هذا الأستاذ independently. ثم أنشئ Course، مع تمرير العنوان، والأستاذ (aggregation)، وعدد المحاضرات (حيث سينشئ المقرر محاضراته الخاصة internally - composition).
اطبع نتيجة
getInfo()، ثم اطبع سطرًا فارغًا، ثم اطبع نتيجةlistLectures().
ستتلقى أربعة inputs بالترتيب: اسم الأستاذ، والقسم، وعنوان المقرر، وعدد المحاضرات.
لاحظ الفرق الأساسي: يتم إنشاء Professor خارج Course ثم تمريره إليه (aggregation: يمكن للأستاذ تدريس مقررات أخرى أو أن يوجد دون هذا المقرر). أما Lectures فتُنشأ داخل constructor الخاص بـ Course (composition. فهي تنتمي حصريًا إلى هذا المقرر، ولن توجد من دونه).
جرّب بنفسك
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 عبر الإنترنت