نمط التركيب
جزء من قسم البرمجة كائنية التوجه في رحلة 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 عندما تحتاج إلى تمثيل تسلسلات هرمية للجزء والكل، وتريد أن يتعامل العملاء مع الكائنات الفردية والتركيبات بشكل موحّد.
التحدي
سهللنبنِ نظام مخطط المؤسسة باستخدام نمط Composite. ستنشئ تسلسلاً هرميًا يمكن فيه التعامل مع كل من الموظفين الأفراد وDepartment (التي تحتوي على موظفين أو أقسام فرعية أخرى) بطريقة موحّدة. وهذا يحاكي كيفية تنظيم الشركات الحقيقية: إذ تحتوي الأقسام على أشخاص وأقسام أخرى، مكوّنةً بنية شجرية.
ستنظّم التعليمات البرمجية عبر ثلاثة ملفات:
OrgComponent.h: عرّف واجهة component التي سيطبّقها كل من الموظفين وDepartment.أنشئ فئة
OrgComponentمجردة تتضمن:getName(): تُرجع اسم componentgetSalary(): تُرجع إجمالي الراتب (بالنسبة إلى الموظفين، راتبهم الخاص؛ وبالنسبة إلى 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 إلى DepartmentgetSalary(): تحسب بشكل递归ي إجمالي رواتب جميع الأعضاءdisplay(): تطبع اسم Department بين قوسين مربعين، ثم تعرض جميع children مع زيادة المسافة البادئة (أضف مسافتين لكل مستوى)
يجب أن يكون تنسيق عرض Department كما يلي:
[indent spaces][Department Name] [children displayed with indent + 2]main.cpp: أنشئ بنية مؤسسة واعرضها.اقرأ أربع مدخلات:
- اسم الشركة (string)
- اسم Department (string)
- اسم وراتب أول موظف (التنسيق:
name,salary) - اسم وراتب ثاني موظف (التنسيق:
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;
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
1أساسيات الـ OOP
الملفات الخارجيةبناء وتجميع (Compilation) لغة C++ملفات الـ Header وملفات الـ Sourceالـ Namespaces والـ Scopeمقدمة في الـ OOP في C++الـ Classes مقابل الـ Objectsالمؤشر 'this'الـ Methods (الدوال الأعضاء)الـ Attributes (بيانات الأعضاء)أساسيات الـ Ctors والـ Dtorsمراجعة - آلة حاسبة بسيطة4خصائص الـ Class
أعضاء الـ Instance مقابل الـ Staticدوال الـ Getters والـ Settersدوال الأعضاء الثابتة (Const)الكلمة المفتاحية Mutableالدوال والمتغيرات الساكنة (Static)الدوال والفئات الصديقة (Friend)مراجعة - مدير الحساب البنكي7الوراثة
الوراثة الأساسيةمستويات الوصول في الوراثةترتيب استدعاء الـ Ctor والـ Dtorإعادة تعريف الدوال (Method Overriding)الدوال الافتراضية والـ VTableالوراثة المتعددةالوراثة الافتراضيةمراجعة - هيكلية الموظفين10نظرة عامة على STL
نظرة عامة وفلسفة STLحاويات STLالـ Iteratorsخوارزميات STLالـ Functors وتعبيرات Lambdaمراجعة - تكرار الكلمات13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factory و Abstract Factoryنمط Builderنمط Observerنمط Strategy2إدارة الذاكرة
ذاكرة Stack مقابل Heapالمؤشرات والمراجعالذاكرة الديناميكية (new/delete)المؤشرات الذكية في C++RAII في C++مراجعة - مدير المصفوفات الديناميكية5التغليف (Encapsulation)
محددات الوصول في C++محددات الوصول بشكل متعمقإخفاء المعلوماتStruct مقابل Classالأصناف المتداخلة والداخليةمراجعة - نظام سجلات الطلاب8تعدد الأشكال (Polymorphism)
تعدد الأشكال: وقت التجميع مقابل وقت التشغيلالتحميل الزائد للدوال (Function Overloading)مراجعة الدوال الافتراضية (Virtual Functions)الدوال الافتراضية البحتة (Pure Virtual Functions)الأصناف المجردة (Abstract Classes)تصميم الواجهات (Interface) في C++التحويل الديناميكي (Dynamic Casting) و RTTIملخص - حاسبة الأشكال11مفاهيم OOP المتقدمة
التركيب مقابل الوراثةالـ Mixins عبر CRTPنمط Pimplمحو النوع (Type Erasure)أصناف Enum والأنواع القويةمعالجة الاستثناءات في OOPتسلسلات الاستثناءات المخصصة14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط قالب الطريقةنمط الحالةنمط التركيبRAII كنمط3المنشئات والموادم
المنشئ الافتراضيالمنشئ ذو المعاملاتمنشئ النسخمنشئ النقلقوائم تهيئة المنشئالمنشئات المفوضةتعمق في الموادمقاعدة الثلاثة / الخمسة / الصفرمراجعة - فئة String6تحميل العوامل (Operator Overloading)
مقدمة في تحميل العواملتحميل العوامل الحسابيةتحميل عوامل المقارنةعوامل Streamتحميل عامل التعيينتحميل العوامل [] و ()عوامل تحويل النوعمراجعة - Matrix Class9القوالب
قوالب الدوالقوالب الأصنافتخصيص القوالبالقوالب المتغيرةأساسيات SFINAE و Type Traitsمراجعة - الحاويات العامةتدرّب بنفسك: مترجم C++ عبر الإنترنت