نمط التركيب
جزء من قسم Object Oriented Programming في رحلة Java على Coddy — الدرس 75 من 87.
يُعد نمط التركيب (Composite Pattern) نمط تصميم هيكلي يتيح لك تركيب الكائنات في هياكل شجرية لتمثيل التسلسلات الهرمية للجزء والكل. فهو يتيح للعملاء معاملة الكائنات الفردية وتركيبات الكائنات بشكل موحد من خلال واجهة مشتركة.
فكر في نظام الملفات: يمكن للمجلد أن يحتوي على ملفات ومجلدات أخرى، والتي يمكن أن تحتوي بدورها على المزيد من الملفات والمجلدات. سواء كنت تتعامل مع ملف واحد أو هيكل مجلد كامل، فأنت تريد إجراء عمليات مثل "get size" بنفس الطريقة. يحدد هذا النمط واجهة 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();
}
}يقوم الكائن المركب (composite) بتفويض العمليات إلى أبنائه، مما يسمح بالهياكل المتكررة. يتفاعل العملاء مع الشجرة دون معرفة ما إذا كانوا يعملون مع ورقة (leaf) أو كائن مركب (composite):
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 مثاليًا لتمثيل الهياكل الهرمية مثل المخططات التنظيمية، أو مكونات واجهة المستخدم (UI components)، أو أنظمة القوائم حيث تحتاج إلى معاملة المجموعات والأفراد بشكل متماثل.
التحدي
سهللنقم ببناء نظام للهيكل التنظيمي باستخدام نمط التركيب (Composite Pattern)! ستقوم بإنشاء هيكل حيث يمكن للأقسام أن تحتوي على موظفين وأقسام فرعية أخرى، مما يسمح لك بحساب إجمالي الرواتب وعرض التسلسل الهرمي بشكل موحد — سواء كنت تنظر إلى موظف واحد أو قسم كامل.
ستقوم بتنظيم الكود الخاص بك عبر أربعة ملفات:
OrganizationComponent.java: قم بتعريف واجهة المكون (interface) التي سيقوم كل من الموظفين والأقسام بتنفيذها. يجب أن تعلن عن طريقتين:showDetails(String indent)لعرض المكون مع مسافة بادئة مناسبة، وgetSalary()التي تعيد إجمالي الراتب كعدد صحيح (integer).Employee.java: قم بإنشاء فئة الورقة (leaf class) التي تمثل العمال الأفراد. يمتلكEmployeeاسماً (String) وراتباً (int)، يتم تعيينهما من خلال منشئ الفئة (constructor). عند استدعاءshowDetails، يجب أن تطبع[indent][name]: $[salary]. تعيد طريقةgetSalaryببساطة راتب الموظف.Department.java: قم بإنشاء الفئة المركبة (composite class) التي يمكنها احتواء كل من الموظفين والأقسام الفرعية. يمتلك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 نفسهما بسلاسة سواء تم استدعاؤهما على موظف واحد أو قسم كامل يحتوي على أقسام فرعية متداخلة. يعالج هيكل التركيب (composite structure) عملية التكرار (recursion) تلقائياً، مما يتيح لك التعامل مع التسلسل الهرمي بأكمله بشكل موحد!
ورقة مرجعية
يُعد نمط التركيب (Composite Pattern) نمط تصميم هيكلي يقوم بتكوين الكائنات في هياكل شجرية لتمثيل التسلسلات الهرمية (جزء من كل). وهو يتيح التعامل مع الكائنات الفردية والتركيبات بشكل موحد من خلال واجهة مشتركة.
يتكون النمط من ثلاثة عناصر رئيسية:
- المكون (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");يقوم المركب بتفويض العمليات إلى أبنائه بشكل متكرر. ويتفاعل العملاء مع الشجرة دون معرفة ما إذا كانوا يتعاملون مع ورقة أو مركب.
حالات الاستخدام: المخططات التنظيمية، مكونات واجهة المستخدم، أنظمة القوائم، أنظمة الملفات - أي تسلسل هرمي يحتاج فيه المجموعات والأفراد إلى معاملة متطابقة.
جرّب بنفسك
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" كجذر (root)
// TODO: إنشاء وإضافة موظفين اثنين إلى Engineering باستخدام بيانات emp1 و emp2
// TODO: إنشاء قسم فرعي باستخدام subDeptName
// TODO: إنشاء وإضافة موظف واحد إلى القسم الفرعي باستخدام بيانات emp3
// TODO: إضافة القسم الفرعي إلى Engineering
// TODO: استدعاء showDetails("") على قسم Engineering
// TODO: طباعة إجمالي الراتب بالتنسيق التالي: Total Salary: $[amount]
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس 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أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المكرر