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

نمط التركيب

جزء من قسم البرمجة كائنية التوجه في رحلة C++ على Coddy. الدرس 101 من 104.

يتيح لك Composite pattern التعامل مع الكائنات الفردية ومجموعات الكائنات بشكل موحّد. فهو يؤلف الكائنات في هياكل شجرية، حيث تشترك كلٌّ من العناصر المفردة والحاويات التي تضم عناصر في الواجهة نفسها. وهذا مثالي لتمثيل التسلسلات الهرمية مثل أنظمة الملفات، أو المخططات التنظيمية، أو مكوّنات واجهة المستخدم components.

يتكوّن النمط من ثلاثة أجزاء رئيسية: واجهة Component التي تحدّد العمليات المشتركة، وفئات Leaf التي تمثّل الكائنات الفردية، وفئات Composite التي تحتوي على children وتفوّض العمليات إليها:

#include <iostream>
#include <memory>
#include <vector>
#include <string>

// واجهة المكون
class FileSystemItem {
public:
    virtual void display(int indent = 0) const = 0;
    virtual int getSize() const = 0;
    virtual ~FileSystemItem() = default;
};

// الورقة - تمثل الملفات الفردية
class File : public FileSystemItem {
    std::string name;
    int size;
public:
    File(const std::string& n, int s) : name(n), size(s) {}
    
    void display(int indent = 0) const override {
        std::cout << std::string(indent, ' ') << name 
                  << " (" << size << " KB)\n";
    }
    int getSize() const override { return size; }
};

// المركب - يحتوي على مكونات أخرى
class Folder : public FileSystemItem {
    std::string name;
    std::vector<std::shared_ptr<FileSystemItem>> children;
public:
    Folder(const std::string& n) : name(n) {}
    
    void add(std::shared_ptr<FileSystemItem> item) {
        children.push_back(item);
    }
    
    void display(int indent = 0) const override {
        std::cout << std::string(indent, ' ') << "[" << name << "]\n";
        for (const auto& child : children) {
            child->display(indent + 2);
        }
    }
    
    int getSize() const override {
        int total = 0;
        for (const auto& child : children) {
            total += child->getSize();
        }
        return total;
    }
};

يخزّن المركّب Folder العناصر الفرعية وينفّذ العمليات من خلال التكرار عليها. عند استدعاء getSize() على مجلد، يحسب بشكل递归ي الحجم الإجمالي لجميع العناصر الموجودة فيه. لا تحتاج شيفرة العميل إلى معرفة ما إذا كانت تتعامل مع ملف أو مجلد - فكلاهما يستجيب للواجهة نفسها.

استخدم Composite عندما تحتاج إلى تمثيل تسلسلات هرمية للجزء والكل، وتريد أن يتعامل العملاء مع الكائنات الفردية والتركيبات بشكل موحّد.

challenge icon

التحدي

سهل

لنبنِ نظام مخطط المؤسسة باستخدام نمط Composite. ستنشئ تسلسلاً هرميًا يمكن فيه التعامل مع كل من الموظفين الأفراد وDepartment (التي تحتوي على موظفين أو أقسام فرعية أخرى) بطريقة موحّدة. وهذا يحاكي كيفية تنظيم الشركات الحقيقية: إذ تحتوي الأقسام على أشخاص وأقسام أخرى، مكوّنةً بنية شجرية.

ستنظّم التعليمات البرمجية عبر ثلاثة ملفات:

  • OrgComponent.h: عرّف واجهة component التي سيطبّقها كل من الموظفين وDepartment.

    أنشئ فئة OrgComponent مجردة تتضمن:

    • getName(): تُرجع اسم component
    • getSalary(): تُرجع إجمالي الراتب (بالنسبة إلى الموظفين، راتبهم الخاص؛ وبالنسبة إلى Department، مجموع جميع الرواتب الموجودة بداخلها)
    • display(int indent = 0): تعرض component مع المسافة البادئة المناسبة

    ضمّن destructor افتراضيًا.

  • Organization.h: طبّق فئتَي leaf وcomposite.

    أنشئ فئة Employee (وهي leaf) تخزّن اسمًا وراتبًا. يجب أن تطبع طريقة display() الخاصة بها معلومات الموظف بهذا التنسيق:

    [indent spaces]- [name] ($[salary])

    أنشئ فئة Department (وهي composite) تخزّن اسمًا ومجموعة من children من نوع OrgComponent باستخدام std::shared_ptr. طبّق ما يلي:

    • add(std::shared_ptr<OrgComponent> component): تضيف child إلى Department
    • getSalary(): تحسب بشكل递归ي إجمالي رواتب جميع الأعضاء
    • display(): تطبع اسم Department بين قوسين مربعين، ثم تعرض جميع children مع زيادة المسافة البادئة (أضف مسافتين لكل مستوى)

    يجب أن يكون تنسيق عرض Department كما يلي:

    [indent spaces][Department Name]
      [children displayed with indent + 2]
  • main.cpp: أنشئ بنية مؤسسة واعرضها.

    اقرأ أربع مدخلات:

    1. اسم الشركة (string)
    2. اسم Department (string)
    3. اسم وراتب أول موظف (التنسيق: name,salary)
    4. اسم وراتب ثاني موظف (التنسيق: name,salary)

    أنشئ هذه البنية: أنشئ شركة (Department من المستوى الأعلى)، وأضف Department فرعيًا إليها، ثم أضف كلا الموظفين إلى ذلك Department الفرعي. بعد ذلك اعرض المؤسسة بأكملها واطبع إجمالي راتب الشركة.

    بعد عرض البنية، اطبع:

    Total Salary: $[amount]

على سبيل المثال، باستخدام المدخلات TechCorp وEngineering وAlice,75000 وBob,65000:

[TechCorp]
  [Engineering]
    - Alice ($75000)
    - Bob ($65000)
Total Salary: $140000

وباستخدام المدخلات StartupInc وDevelopment وCarol,80000 وDave,70000:

[StartupInc]
  [Development]
    - Carol ($80000)
    - Dave ($70000)
Total Salary: $150000

لاحظ كيف تعمل getSalary() بطريقة موحّدة سواء استُدعيت على موظف أو على Department: إذ يجمع Department الرواتب تلقائيًا من جميع أعضائه. ولا تحتاج شيفرة العميل إلى التمييز بين الموظفين الأفراد وDepartment بأكمله عند حساب الإجماليات أو عرض التسلسل الهرمي.

جرّب بنفسك

#include <iostream>
#include <string>
#include <memory>
#include <sstream>
#include "Organization.h"

int main() {
    // اقرأ المدخلات
    std::string companyName;
    std::string departmentName;
    std::string employee1Input;
    std::string employee2Input;
    
    std::getline(std::cin, companyName);
    std::getline(std::cin, departmentName);
    std::getline(std::cin, employee1Input);
    std::getline(std::cin, employee2Input);
    
    // دالة lambda مساعدة لتحليل تنسيق "name,salary"
    auto parseEmployee = [](const std::string& input) -> std::pair<std::string, int> {
        size_t commaPos = input.find(',');
        std::string name = input.substr(0, commaPos);
        int salary = std::stoi(input.substr(commaPos + 1));
        return {name, salary};
    };
    
    auto [name1, salary1] = parseEmployee(employee1Input);
    auto [name2, salary2] = parseEmployee(employee2Input);
    
    // TODO: أنشئ الشركة كـ Department من المستوى الأعلى
    
    // TODO: أنشئ قسماً فرعياً
    
    // TODO: أنشئ كائني Employee باستخدام البيانات المحللة
    
    // TODO: أضف الموظفين إلى القسم الفرعي
    
    // TODO: أضف القسم الفرعي إلى الشركة
    
    // TODO: اعرض هيكل المنظمة بالكامل
    
    // TODO: اطبع إجمالي الراتب بالتنسيق: Total Salary: $[amount]
    
    return 0;
}
quiz iconاختبر نفسك

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

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

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