Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

نمط التركيب

جزء من قسم 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)، أو أنظمة القوائم حيث تحتاج إلى معاملة المجموعات والأفراد بشكل متماثل.

challenge icon

التحدي

سهل

لنقم ببناء نظام للهيكل التنظيمي باستخدام نمط التركيب (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]
    }
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming