Menu
CoddyTech

نمط التركيب

جزء من قسم البرمجة كائنية التوجه في رحلة 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");

يُعد نمط التركيب مثاليًا لتمثيل التسلسلات الهرمية مثل المخططات التنظيمية، ومكونات واجهة المستخدم، وأنظمة القوائم، حيث تحتاج إلى التعامل مع المجموعات والأفراد بالطريقة نفسها.

challenge icon

التحدي

سهل

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

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

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: مترجم Java عبر الإنترنت