اليوم سنتعلم شيئًا بالغ الأهمية، يُسمى العلاقات. سأجعلها بسيطة لكم. هناك ثلاث علاقات سنتعلمها. هناك علاقة الاعتماد (Dependency)، وهناك التجميع (Aggregation)، وهناك الوراثة (Inheritance). بسيط جدًا!
الاعتماد يقول "يستخدم." التجميع يقول "يمتلك." الوراثة تقول "هو نوع من."
🔧
الاعتماد (Dependency)
"uses"
الفئة (Class) A تستخدم الفئة B بصورة مؤقتة (كمعامل دالة أو متغير محلي). B ليست جزءًا من هوية (Identity) A — إنها مجرد أداة.
📦
التجميع (Aggregation)
"has"
الفئة A تمتلك فئة B مخزّنة كحقل (Field). يأتي بقوتين: التجميع (Aggregation) (ضعيف) والتركيب (Composition) (قوي).
🧬
الوراثة (Inheritance)
"is"
الفئة A هي نوع من الفئة B. يُدرَّس في الوحدة السادسة.
📌 للتجميع (Aggregation) قوتان
كلٌّ من التجميع (Aggregation) والتركيب (Composition) علاقة "يمتلك" — فكلاهما يعني أن فئة ما تخزّن كائنًا آخر كحقل (Field). الفرق هو مدة حياة الكائن المحتوى.
📦
التجميع (Aggregation)
"has-a" (weak)
الكائن المحتوى يمكنه الوجود بشكل مستقل. يمكن مشاركته بين عدة مالكين. رمز الماسة المفتوحة ◇ في مخطط الفئة (UML Class Diagram).
💎
التركيب (Composition)
"owns-a" (strong)
الكائن المحتوى لا يمكنه الوجود بدون مالكه. يموت حين يموت المالك. رمز الماسة المملوءة ◆ في مخطط الفئة (UML Class Diagram).
🎓 السيناريو العملي — أربع فئات
👨🏫
الأستاذ
تخيّل أن لدينا فئة (Class) باسم Student. اقترح الطلاب: "ينبغي أن نقسّم الطالب إلى كائنات أصغر — الطالب لديه Address وName، وكلاهما يمكن أن يكون كائنًا!"
👨🏫
الأستاذ
إذن لدينا الآن فئة Address — تحتوي على اسم الشارع والمدينة (مثلاً "جدة") ورقم الوحدة (مثلاً 100). ولدينا فئة Name — تحتوي على الاسم الأول والأوسط والأخير. ولدينا فئة BMICalculator — تمرّر لها الوزن بالكيلوجرام والطول بالسنتيمتر وتحصل على مؤشر كتلة الجسم (BMI). هذا كل شيء!
إذن كم عدد الفئات؟ أربع فئات: Student، Address، Name، BMICalculator. الآن — ما هي العلاقة بينها؟
أربع فئات — ثلاثة أنواع مختلفة من العلاقات مع Student
2
الاعتماد (Dependency) — "يستخدم"
لتحديد نوع العلاقة التي تربط BMICalculator بـStudent، لا نبدأ بالكود — بل نبدأ بالنموذج (Model). نطرح سؤالًا فلسفيًا واحدًا: "هل BMICalculator جزء من هوية (Identity) الطالب؟" الإجابة على هذا السؤال تحدد كل شيء.
🏫 الخطوة الأولى — سؤال الهوية (Identity): ما الذي يُعرّف الطالب؟
👨🏫
الأستاذ
دعني أسألكم: ما الذي يُعرّف الطالب؟ ما الأشياء التي تجعل هذا الطالب هو هو؟
🙋
الطلاب
اسمه، ورقم هويته، وعنوانه، ودرجاته!
👨🏫
الأستاذ
بالضبط. الاسم، والرقم، والعنوان، والدرجات — هذه هي الأشياء التي تُشكّل هوية (Identity) الطالب. إنها تصف من هو. الآن — هل يُعرّفه BMICalculator؟ هل هو جزء من كينونته؟
🙋
الطلاب
لا… BMICalculator ليس من هو. إنه مجرد شيء يستخدمه.
👨🏫
الأستاذ
صحيح! تخيّل الآن أن BMICalculator جهاز — آلة واقفة في ممر الجامعة. يقترب منها الطالب، يُدخل وزنه وطوله، يحصل على قيمة مؤشر كتلة الجسم (BMI)، ثم يمضي. الآلة ليست جزءًا منه. لا يحملها. لا يمتلكها. أي طالب يمكنه استخدامها. إنها لا علاقة لها بهويته (Identity) أبدًا.
🧠 الخطوة الثانية — الفلسفة أولًا، الكود ثانيًا
👨🏫
الأستاذ
إذن القرار يُتخذ على مستوى النموذج (Model)، لا على مستوى الكود. لأن BMICalculator ليس جزءًا من هوية (Identity) الطالب، فنحن لا نخزّنه كحقل (Field) في بيانات الطالب. نستخدمه فقط حين نحتاجه — نمرّره كـمعامل دالة (Method Parameter). هذا ما ستراه في الكود. لكن السبب فلسفي — ليس نحويًا.
🙋
الطالب
أستاذ — لكن ماذا لو كتبته كحقل (Field) في البيانات؟ هل سيصبح تجميعًا (Aggregation) حينئذ؟
👨🏫
الأستاذ
سؤال ممتاز — والإجابة تعتمد على الواقع. اسأل نفسك: هل يمتلك كل طالب جهاز BMICalculator شخصيًا؟ لو كان كل طالب يحمل جهازه الخاص — الذي ينتمي له وحده دون غيره — لكان منطقيًا تخزينه كحقل (Field)، وستكون العلاقة تجميعًا (Aggregation). لكن انظر ما هو BMICalculator في حقيقته. لا يحتوي على أي بيانات خاصة بطالب. لا ارتباط له بأي طالب بعينه. إنه أداة مشتركة — كالآلة في الممر التي يمكن لأي طالب استخدامها. لا يمتلكها أحد. لا يحملها أحد كجزء من هويته. فلو كتبتها كحقل (Field)، سيُترجَم الكود — لكنك ستكون قد وصفت شيئًا غير حقيقي. الكود المفترض أن يعكس النموذج. إذا كان الكود يتناقض مع النموذج، فالكود خاطئ.
🙋
الطلاب
إذن القاعدة: إذا لم يكن جزءًا من الهوية (Identity) ← لا تجعله حقلًا (Field) ← استخدمه كمعامل (Parameter) ← وهذا هو الاعتماد (Dependency)!
👨🏫
الأستاذ
بالضبط! ✅ ولاحظ — يمكننا الذهاب أبعد من ذلك وجعل getBMI() دالة ثابتة (static). لأن BMICalculator لا ارتباط له بأي طالب محدد، لا نحتاج حتى إلى كائن — ننادي مباشرةً BMICalculator.getBMI(weight, height). هذا أكثر الطرق طبيعيةً للتعبير عن كونه أداة مساعدة بحتة، بلا ملكية ولا علاقة بالهوية (Identity).
⚠️ المبدأ الأساسي — النموذج هو الذي يحدد العلاقة، لا الكود
اتجاه التفكير: الفلسفة ← النموذج ← الكود. أبدًا بالعكس. إذا صادفت وكتبت BMICalculator كحقل (Field)، فهذا لا يجعله تجميعًا (Aggregation) — بل يجعله خطأً في النمذجة.
السهم المتقطع المفتوح = الاعتماد (Dependency) — علاقة "يستخدم"
كلٌّ من التجميع (Aggregation) والتركيب (Composition) علاقة "يمتلك" — تخزّن الفئة كائنًا آخر كحقل (Field) في بياناتها. السؤال الجوهري دائمًا: "هل يمكن للكائن المحتوى أن يبقى حيًّا بعد غياب مالكه؟"
🏫 الخطوة الأولى — ماذا يعني "التجميع (Aggregation)"؟
👨🏫
الأستاذ
سؤال واحد — ماذا يعني "التجميع (Aggregation)"؟ لدينا مصطلحان يجب فهمهما: الفئة المُجمِّعة (Aggregating class) والفئة المُجمَّعة (Aggregated class).
التجميع (Aggregation) يعني تجميع — جمع أشياء معًا. في حالتنا، فئة Student هي الفئة المُجمِّعة — تجمع كائنات أخرى بداخلها. أما Address وName فهما الفئتان المُجمَّعتان — يُجمَعان داخل الطالب.
🏠 الخطوة الثانية — تشبيه الشقة: التركيب (Composition) مقابل التجميع (Aggregation)
👨🏫
الأستاذ
دعني أطرح عليكم سؤالًا بسيطًا جدًا. هل يمكنكم تخيّل اسم (Name) بدون طالب؟ هل يمكننا أن نقول "هذا علي فهد" — لكن أين هو؟ ينتمي لمن؟ لمن هذا الاسم؟ لا يوجد طالب — لا يوجد اسم!
🙋
الطالب
هذا صحيح يا أستاذ. "علي فهد" لا يمكنه الوجود بمعزل عن صاحبه. الاسم لا معنى له إلا إذا كان هناك شخص — طالب!
👨🏫
الأستاذ
تخيّل الآن ثلاثة طلاب — علي وخالد وفهد — يسكنون في نفس الشقة في شارع الجامعة، وحدة 100، جدة. كلهم يشتركون في نفس العنوان.
الآن علي يغادر الجامعة. ماذا يحدث لعنوان "شارع الجامعة، وحدة 100، جدة"؟ هل يختفي؟
🙋
الطلاب
لا! العنوان ما زال موجودًا — خالد وفهد ما زالا يسكنان هناك! العنوان بقي حيًّا حتى بعد مغادرة علي.
👨🏫
الأستاذ
بالضبط! ✅ لكن لو مات علي — فلا بد أن يموت اسم "علي فهد" معه. هل ترون العلاقة؟
🔵 Address → تجميع (Aggregation) (ضعيف): يمكن مشاركة العنوان وهو يوجد باستقلالية بعيدًا عن أي طالب بعينه.
💚 Name → تركيب (Composition) (قوي): الاسم لا يمكنه الوجود بدون صاحبه — يموت مع الطالب.
🙋
الطلاب
رائع! 😮 وBMICalculator — ليس جزءًا من الطالب أصلًا، أليس كذلك؟ ليس تجميعًا (Aggregation) أيضًا. يُستخدَم فقط بصورة مؤقتة — إنه اعتماد (Dependency)!
👨🏫
الأستاذ
ممتاز! حاسبة مؤشر كتلة الجسم ليست سوى أداة تُستخدَم بصورة مؤقتة لحساب قيمة — لا تُسهم في هوية (Identity) الطالب أو تعريفه. هذا بالضبط هو سبب كونها اعتمادًا (Dependency)، لا تجميعًا (Aggregation). 🎉
أستاذ — أفهم المفهوم. لكن لديّ تحدٍّ. حين أذهب لكتابة كود Java… كيف أجعل كائن Name يموت فعلًا حين يُدمَّر Student؟ وكيف أجعل Address يبقى حيًّا؟ مخطط الفئة (UML) يظهر الماسة ◆ أو ◇ — لكن كيف أُعبّر عن ذلك في الكود؟
👨🏫
الأستاذ
سؤال ممتاز — هذا بالضبط ما يجب أن تفهمه لتصبح مبرمجًا جيدًا. الإجابة قاعدة واحدة: كل شيء يتعلق بـأين تكتب new.
للـتركيب (Composition) ◆ — تكتب new Name(...)داخل مُنشئ (constructor) الـStudent. كائن Name هذا أنشأه الطالب، يملكه الطالب، ولا مرجع له خارجه. حين يختفي الطالب — يختفي الاسم معه. لا أحد آخر يحتفظ بمرجع إليه.
للـتجميع (Aggregation) ◇ — تُنشئ كائن Addressخارجيًا، في main أو في مكان آخر، ثم تمرّره إلى المُنشئ (constructor). الآن يمكن لعدة طلاب الاحتفاظ بمرجع لنفس كائن العنوان. حين يُدمَّر طالب واحد، العنوان ما زال حيًّا — لأن الطلاب الآخرين ما زالوا يحتفظون بمرجعهم إليه.
بالضبط! ✅ هذه هي القاعدة الوحيدة التي يجب عليك تطبيقها. انظر إلى الكود بعناية — مُنشئ Student يستقبل first وlast كمعاملَي String عاديَّين، ثم ينادي new Name(first, last) هناك في الداخل مباشرةً. هذا هو التركيب (Composition). أما للعنوان، فيستقبل كائن Address جاهزًا أُنشئ خارجيًا — هذا هو التجميع (Aggregation). مُنشئ واحد، علاقتان مختلفتان، يُعبَّر عنهما باختلاف صغير واحد في مكان كتابة new.
📄 Student.java — التركيب (Composition) مقابل التجميع (Aggregation) في الكود
public classStudent {
private int id;
privateName name; // ◆ تركيب (COMPOSITION) — سيُنشأ بالداخلprivateAddress address; // ◇ تجميع (AGGREGATION) — سيُستقبل من الخارج// Name يُنشأ هنا داخل المُنشئ (constructor) → تركيب (Composition)
// Address يُمرَّر من الخارج → تجميع (Aggregation)publicStudent(int id, String first, String last, Address address) {
this.id = id;
this.name = newName(first, last); // ◆ تركيب: يُنشأ بالداخلthis.address = address; // ◇ تجميع: كائن مشترك من الخارج
}
publicStringgetFullName() { return name.getFullName(); }
publicAddressgetAddress() { return address; }
// الاعتماد (Dependency): BMICalculator يُستخدَم مؤقتًا كمعامل دالة (Method Parameter) — لا يُخزَّنpublic doublecalculateBMI(BMICalculator calc) {
return calc.getBMI();
}
}
// ── في main ────────────────────────────────────────────────────────────
// كائن عنوان واحد — مشترك بين عدة طلاب (تجميع Aggregation)Address sharedAddress = newAddress("Al-Jami'a St", "Jeddah", 100);
Student ali = newStudent(1, "Ali", "Fahad", sharedAddress);
Student khalid = newStudent(2, "Khalid", "Nasser", sharedAddress);
Student fahad = newStudent(3, "Fahad", "Salem", sharedAddress);
// الثلاثة يشتركون في نفس كائن العنوان — لو اختفى ali، يبقى khalid و fahad مع العنوان ✅
// كل طالب يمتلك كائن اسمه الخاص — أُنشئ داخل مُنشئه الخاص ◆
✅ قاعدة المبرمج الأساسية
التركيب (Composition) ◆ → أنشئ الكائن داخل المُنشئ (constructor) بـnew. الكائن حصري لهذا المالك.
التجميع (Aggregation) ◇ → استقبل الكائن من الخارج كمعامل للمُنشئ (constructor). يمكن مشاركته.
الاعتماد (Dependency) - - → → استقبله كمعامل دالة (Method Parameter) فقط. لا يُخزَّن أبدًا كحقل (Field). ليس جزءًا من هوية (Identity) الكائن.
4
التجميع الذاتي (Self-Aggregation) — فئة تُشير إلى نفسها
ننتقل الآن إلى شيء مثير — التجميع الذاتي (Self-Aggregation). ماذا يعني؟ إنه تجميع بين كائنات من نفس الفئة. تحتوي الفئة على حقل (Field) بياناتي نوعه هو نفس الفئة. قد يبدو هذا غريبًا — لكنه طبيعي جدًا حين ترى المثال!
🏫 نقاش في الفصل — "كيف نُعيّن مشرفًا؟"
👨🏫
الأستاذ
دعوني أبسّط الأمر. لديّ ثلاثة طلاب: علي وخالد وفهد. تخيّل فقط أن لديهم رقمًا واسمًا — هذا كل شيء. أريد الآن أن أُعيّن عليمشرفًا على خالد وفهد. كيف أخزّن هذه المعلومة في فئة (Class) الـStudent؟
🙋
الطالب
أستاذ، لماذا لا نضيف حقلًا (Field) يُسمى supervisorId من نوع int؟ نخزّن فقط رقم المشرف!
👨🏫
الأستاذ
قد ينجح ذلك — لكن فكّر في الأمر. ماذا لو أردت في يوم ما معرفة عمر المشرف؟ أو معدّله؟ أو عنوانه؟ أي تفصيل عنه؟ لو خزّنت الرقم فقط، ستضطر إلى البحث بين كل الطلاب في كل مرة للعثور على تلك المعلومة. ستصبح الأمور معقدة جدًا وبطيئة!
📄 السيناريو 1 — supervisorId : int ❌ نهج محدود
public classStudent {
private int id;
privateString name;
private int supervisorId; // يخزّن الرقم فقط — لا تفاصيل أخرىpublicStudent(int id, String name) {
this.id = id;
this.name = name;
}
public voidsetSupervisorId(int supervisorId) {
this.supervisorId = supervisorId;
}
public intgetSupervisorId() { return supervisorId; }
public intgetId() { return id; }
publicStringgetName() { return name; }
}
// ── في main ──────────────────────────────────────────────────────────Student ali = newStudent(1, "Ali");
Student khaled = newStudent(2, "Khaled");
Student fahad = newStudent(3, "Fahad");
khaled.setSupervisorId(1); // رقم علي
fahad.setSupervisorId(1); // رقم علي// ❌ المشكلة: للحصول على اسم المشرف يجب البحث يدويًا!int supId = khaled.getSupervisorId(); // يُرجع 1 — لكن ماذا بعد؟// تحتاج مصفوفة أو حلقة للعثور على ذلك الطالب — بطيء جدًا!Student[] allStudents = {ali, khaled, fahad};
for (Student s : allStudents) {
if (s.getId() == supId) {
System.out.println(s.getName()); // "Ali" — لكن بأي ثمن؟
}
}
👨🏫
الأستاذ
الآن — فكّر! المشرف هو أيضًا طالب، أليس كذلك؟ المشرف هو Student. إذن… لماذا لا نُعرّف حقل (Field) بيانات يُسمى supervisor، لكن نجعل نوعهStudent؟ هذا يُعطيك كائن المشرف بالكامل بكل تفاصيله!
🙋
الطلاب
لحظة — حقل (Field) من نوع Studentداخل فئة (Class) الـStudent نفسها؟! 🤯 كائنات داخل كائنات من نفس النوع!
👨🏫
الأستاذ
بالضبط! 🎉 هذا ما يُسمى التجميع الذاتي (Self-Aggregation)! أمر رائع — كائنات داخل كائنات من نفس الفئة. دعوني أريكم كيف يعمل في الكود.
التجميع الذاتي (Self-aggregation): Student لديها حقل (Field) من نوع Student — 0..1 تعني أن للطالب صفرًا أو مشرفًا واحدًا
public classStudent {
private int id;
privateString name;
privateStudent supervisor; // ← تجميع ذاتي (self-aggregation): نفس الفئة (Class) كنوع الحقل (Field)!publicStudent(int id, String name) {
this.id = id;
this.name = name;
// supervisor يبدأ بـ null — يمكن تعيينه لاحقًا
}
public voidsetSupervisor(Student supervisor) {
this.supervisor = supervisor;
}
publicStudentgetSupervisor() { return supervisor; }
public intgetId() { return id; }
publicStringgetName() { return name; }
}
// ── في main ──────────────────────────────────────────────────────────Student ali = newStudent(1, "Ali");
Student khaled = newStudent(2, "Khaled");
Student fahad = newStudent(3, "Fahad");
// تعيين علي مشرفًا لخالد وفهد
khaled.setSupervisor(ali);
fahad.setSupervisor(ali);
// الآن يمكن الوصول لكل تفاصيل المشرف عبر سلسلة الكائنات!
System.out.println(khaled.getSupervisor().getName()); // "Ali"
System.out.println(fahad.getSupervisor().getId()); // 1
📦 الكائنات في الذاكرة — مراجع متسلسلة:
khaled : Student
id2
name"Khaled"
supervisor→ ali
→
ali : Student
id1
name"Ali"
supervisornull
🔮 ستقابل هذا مجددًا — القوائم المترابطة (Linked Lists)!
ستلتقي بالتجميع الذاتي (Self-Aggregation) مجددًا في مساق هياكل البيانات الفصل القادم، حين تدرس القوائم المترابطة (Linked Lists). كل Node تحتفظ ببعض البيانات ومرجع للـNodeالتالية — وهذا بالضبط نفس نمط التجميع الذاتي! المفاهيم التي تتعلمها الآن هي الأساس.
5
التعددية (Multiplicity) — كم عدد الكائنات المشاركة؟
قبل الانتقال إلى الموضوع التالي، شرح الأستاذ مفهومًا آخر في مخطط الفئة (UML Class Diagram) — التعددية (Multiplicity). هذا يُجيب عن السؤال: "كم عدد الكائنات من فئة معينة مشاركة في علاقة ما؟"
🏫 نقاش في الفصل — ما هي التعددية (Multiplicity)؟
👨🏫
الأستاذ
في مخطط الفئة (UML)، حين نرسم علاقة بين فئتين، يمكن أن تكون العلاقة متعدد-لمتعدد أو واحد-لمتعدد. يجب أن نوضح هذا للطالب. لكل فئة مشاركة في العلاقة، نكتب رقمًا — أو نطاقًا — عند طرفها من خط العلاقة. هذا ما يُسمى التعددية (Multiplicity).
👨🏫
الأستاذ
على سبيل المثال — المعلم (Teacher) يمكنه تدريس صفر إلى ثلاثة مقررات (0..3). كل مقرر (Course) لديه معلم واحد بالضبط (1). يجب أن يضم المقرر ما بين 5 و60 طالبًا (5..60). والطالب يمكنه التسجيل في عدد كبير من المقررات (*). هذه هي التعددية (Multiplicity)!
📐 ما هي التعددية (Multiplicity)?
التعددية (Multiplicity) تُحدد عدد النسخ من فئة يمكن أن ترتبط بنسخة واحدة من فئة أخرى. تُكتب عند طرفَي خط العلاقة في مخطط الفئة (UML). كل فئة مشاركة في العلاقة لها تعدديتها المحددة.
الرمز
المعنى
مثال
1
واحد بالضبط
للمقرر معلم واحد بالضبط
0..1
صفر أو واحد (اختياري)
الطالب قد يكون لديه صفر أو مشرف واحد
* أو 0..*
صفر أو أكثر
الطالب يمكنه التسجيل في عدد كبير من المقررات
1..*
واحد أو أكثر
يجب أن تضم الكلية معلمًا واحدًا على الأقل
5..60
بين 5 و60
قسم المقرر يضم 5 إلى 60 طالبًا
0..3
صفر إلى ثلاثة
المعلم يُدرّس 0 إلى 3 مقررات
🎓 مثال: Teacher → Course → Student
📝 كيف تقرأ أرقام التعددية (Multiplicity)
اقرأ كل رقم من منظور الفئة الأخرى.
الرقم 1 بجانب Teacher يعني: "من منظور المقرر (Course)، لديه معلم واحد بالضبط."
الرقم 0..3 بجانب Course يعني: "من منظور المعلم (Teacher)، يُدرّس 0 إلى 3 مقررات."
الرقم 5..60 بجانب Student يعني: "من منظور المقرر (Course)، يضم 5 إلى 60 طالبًا مسجلًا."
الرقم * بجانب Course يعني: "من منظور الطالب (Student)، يمكنه التسجيل في أي عدد من المقررات."
"يستخدم" · "يمتلك" · "هو نوع من" — بسيط! التجميع (Aggregation) له قوتان: ضعيف (تجميع) وقوي (تركيب).
🔧
الاعتماد (Dependency) — "يستخدم"
استخدام مؤقت كمعامل دالة (Method Parameter). لا يُخزَّن كحقل (Field). ليس جزءًا من هوية (Identity) الكائن. يمكن استبداله بدالة ثابتة (static method).
📦
التجميع (Aggregation) ◇ — "has-a"، ضعيف
الكائن المحتوى يبقى مستقلًا ويمكن مشاركته (مثل: Address). يُنشأ خارجيًا ويُمرَّر. ماسة مفتوحة في مخطط الفئة (UML).
💎
التركيب (Composition) ◆ — "owns-a"، قوي
الكائن المحتوى لا يمكنه الوجود بدون المالك (مثل: Name). يُنشأ داخل المُنشئ (constructor) بـnew. ماسة مملوءة في مخطط الفئة (UML).
🔄
التجميع الذاتي (Self-Aggregation)
فئة (Class) تحتوي على حقل (Field) من نوعها الخاص (مثل: Student supervisor داخل Student). كائنات داخل كائنات من نفس النوع. أساس القوائم المترابطة (Linked Lists)!
🔢
التعددية (Multiplicity)
تُحدد عدد الكائنات من فئة مشاركة في علاقة. تُكتب عند طرفَي خط العلاقة في مخطط الفئة (UML): 1، 0..1، *، 1..*، 5..60.