نمط التركيب
جزء من قسم البرمجة كائنية التوجه في رحلة Java على Coddy. الدرس 75 من 87.
نمط المركب هو نمط تصميم هيكلي يتيح لك تركيب الكائنات في بُنى شجرية لتمثيل التسلسلات الهرمية التي تتألف من أجزاء وكُلّيات. ويسمح للعملاء بالتعامل مع الكائنات الفردية ومجموعات الكائنات بطريقة موحّدة عبر واجهة مشتركة.
فكّر في نظام الملفات: يمكن أن يحتوي المجلد على ملفات ومجلدات أخرى، ويمكن لهذه المجلدات أن تحتوي على مزيد من الملفات والمجلدات. سواء كنت تتعامل مع ملف واحد أو مع بنية مجلدات كاملة، فأنت تريد تنفيذ عمليات مثل «الحصول على الحجم» بالطريقة نفسها. يحدّد هذا النمط واجهة Component، وكائنات Leaf (عناصر منفردة)، وكائنات Composite (حاويات تضم عناصر فرعية):
interface FileComponent {
void display(String indent);
int getSize();
}
class File implements FileComponent {
private String name;
private int size;
public File(String name, int size) {
this.name = name;
this.size = size;
}
public void display(String indent) {
System.out.println(indent + name + " (" + size + "KB)");
}
public int getSize() {
return size;
}
}
class Folder implements FileComponent {
private String name;
private List<FileComponent> children = new ArrayList<>();
public Folder(String name) {
this.name = name;
}
public void add(FileComponent component) {
children.add(component);
}
public void display(String indent) {
System.out.println(indent + name + "/");
for (FileComponent child : children) {
child.display(indent + " ");
}
}
public int getSize() {
return children.stream().mapToInt(FileComponent::getSize).sum();
}
}يفوّض المركب العمليات إلى أبنائه، مما يتيح إنشاء هياكل عودية. يتفاعل العملاء مع الشجرة دون معرفة ما إذا كانوا يتعاملون مع عنصر ورقي أم مركب:
Folder root = new Folder("Documents");
root.add(new File("resume.pdf", 150));
Folder photos = new Folder("Photos");
photos.add(new File("vacation.jpg", 2000));
root.add(photos);
root.display("");
System.out.println("Total: " + root.getSize() + "KB");يُعد نمط التركيب مثاليًا لتمثيل التسلسلات الهرمية مثل المخططات التنظيمية، ومكونات واجهة المستخدم، وأنظمة القوائم، حيث تحتاج إلى التعامل مع المجموعات والأفراد بالطريقة نفسها.
التحدي
سهللنبنِ نظامًا لمخطط تنظيمي باستخدام نمط Composite! ستنشئ بنية يمكن فيها للأقسام أن تضم موظفين وأقسامًا فرعية أخرى، مما يتيح لك حساب إجمالي الرواتب وعرض التسلسل الهرمي بطريقة موحّدة، سواء كنت تنظر إلى موظف واحد أو إلى قسم كامل.
ستنظّم الكود عبر أربعة ملفات:
OrganizationComponent.java: عرّف واجهة المكوّن التي سيطبّقها كل من الموظفين والأقسام. ينبغي أن تعلن عن طريقتين:showDetails(String indent)لعرض المكوّن مع المسافة البادئة المناسبة، وgetSalary()التي تُعيد إجمالي الراتب عددًا صحيحًا.Employee.java: أنشئ الفئة الطرفية التي تمثّل العاملين الأفراد. لدىEmployeeاسم (String) وراتب (int)، ويُحدَّدان كلاهما عبر المُنشئ. عند استدعاءshowDetails، ينبغي أن تطبع[indent][name]: $[salary]. أما الطريقةgetSalaryفتعيد راتب الموظف ببساطة.Department.java: أنشئ الفئة المركّبة التي يمكنها احتواء الموظفين والأقسام الفرعية. لدىDepartmentاسم (String)، وتحتفظ بقائمة من الأبناء من نوعOrganizationComponent. ينبغي أن تتضمن طريقةadd(OrganizationComponent component)لإضافة الأعضاء. عند استدعاءshowDetails، تطبع[indent][name] Department، ثم تستدعيshowDetailsلكل ابن مع زيادة المسافة البادئة (أضف مسافتين). تعيد الطريقةgetSalaryمجموع رواتب جميع الأبناء.Main.java: أنشئ مؤسستك! ستتلقى أربعة مُدخلات: اسمي موظفين مع راتبيهما، واسم قسم فرعي مع موظف واحد.أنشئ قسمًا باسم "Engineering" ليكون الجذر. أضف إليه موظفين باستخدام زوجَي الاسم والراتب الأولين. ثم أنشئ قسمًا فرعيًا باستخدام المُدخل الثالث، وأضف إليه موظفًا واحدًا باستخدام زوج الاسم والراتب الرابع، ثم أضف هذا القسم الفرعي إلى Engineering.
استدعِ
showDetails("")على قسم Engineering، ثم اطبع إجمالي الراتب بالتنسيقTotal Salary: $[amount].
ستتلقى المُدخلات بالترتيب التالي: اسم employee1 (String)، راتب employee1 (int)، اسم employee2 (String)، راتب employee2 (int)، اسم القسم الفرعي (String)، اسم employee3 (String)، راتب employee3 (int).
على سبيل المثال، مع المُدخلات Alice و75000 وBob و65000 وQA وCharlie و55000، سيكون الناتج:
Engineering Department
Alice: $75000
Bob: $65000
QA Department
Charlie: $55000
Total Salary: $195000لاحظ كيف تعمل الطريقتان showDetails وgetSalary بسلاسة سواء استُدعيتا على موظف واحد أو على قسم كامل يحتوي على أقسام فرعية متداخلة. تتولى البنية المركّبة معالجة الاستدعاء التكراري تلقائيًا، مما يتيح لك التعامل مع التسلسل الهرمي بأكمله بطريقة موحّدة!
جرّب بنفسك
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// اقرأ المدخلات
String emp1Name = scanner.nextLine();
int emp1Salary = Integer.parseInt(scanner.nextLine());
String emp2Name = scanner.nextLine();
int emp2Salary = Integer.parseInt(scanner.nextLine());
String subDeptName = scanner.nextLine();
String emp3Name = scanner.nextLine();
int emp3Salary = Integer.parseInt(scanner.nextLine());
// TODO: أنشئ القسم "Engineering" بوصفه الجذر
// TODO: أنشئ موظفين وأضفهما إلى Engineering باستخدام بيانات emp1 وemp2
// TODO: أنشئ قسمًا فرعيًا باستخدام subDeptName
// TODO: أنشئ موظفًا واحدًا وأضفه إلى القسم الفرعي باستخدام بيانات emp3
// TODO: أضف القسم الفرعي إلى Engineering
// TODO: استدعِ showDetails("") على القسم Engineering
// TODO: اطبع إجمالي الرواتب بالصيغة: Total Salary: $[amount]
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
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 عبر الإنترنت