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

نمط التركيب

جزء من قسم البرمجة كائنية التوجه في رحلة Java على Coddy. الدرس 75 من 87.

نمط 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();
    }
}

يفوّض composite العمليات إلى أبنائه، مما يتيح هياكل递归ية. يتفاعل العملاء مع الشجرة من دون معرفة ما إذا كانوا يعملون مع ورقة أم مع 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 مثاليًا لتمثيل التسلسلات الهرمية مثل المخططات التنظيمية، أو مكونات واجهة المستخدم، أو أنظمة القوائم، حيث تحتاج إلى التعامل مع المجموعات والأفراد بالطريقة نفسها.

challenge icon

التحدي

سهل

لنَبْنِ نظامًا للمخطط التنظيمي باستخدام نمط Composite! ستنشئ بنية يمكن أن تحتوي فيها الأقسام على الموظفين والأقسام الفرعية الأخرى، مما يتيح لك حساب إجمالي الرواتب وعرض التسلسل الهرمي بطريقة موحّدة، سواء كنت تنظر إلى موظف واحد أو إلى قسم كامل.

ستنظّم التعليمات البرمجية الخاصة بك عبر أربعة ملفات:

  • OrganizationComponent.java: عرّف واجهة component التي سيطبّقها كل من الموظفين والأقسام. يجب أن تعلن عن طريقتين: showDetails(String indent) لعرض component مع المسافة البادئة المناسبة، وgetSalary() التي تُرجع إجمالي الراتب كعدد صحيح.
  • Employee.java: أنشئ فئة leaf تمثّل العمال الأفراد. يحتوي Employee على اسم (String) وراتب (int)، ويتم تعيينهما من خلال constructor. عند استدعاء showDetails، يجب أن تطبع [indent][name]: $[salary]. تُرجع طريقة getSalary راتب الموظف ببساطة.
  • Department.java: أنشئ فئة Composite يمكنها الاحتفاظ بكل من الموظفين والأقسام الفرعية. يحتوي 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 معالجة الاستدعاء التكراري تلقائيًا، مما يتيح لك التعامل مع التسلسل الهرمي بأكمله بطريقة موحّدة!

جرّب بنفسك

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: Create the "Engineering" department as the root
        
        // TODO: إنشاء موظفَين وإضافتهما إلى Engineering باستخدام بيانات emp1 و emp2
        
        // TODO: إنشاء قسم فرعي باستخدام subDeptName
        
        // TODO: إنشاء موظف واحد وإضافته إلى القسم الفرعي باستخدام بيانات emp3
        
        // TODO: إضافة القسم الفرعي إلى Engineering
        
        // TODO: Call showDetails("") on the Engineering department
        
        // TODO: طباعة إجمالي الراتب بالتنسيق: Total Salary: $[amount]
    }
}
quiz iconاختبر نفسك

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

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

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