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

نمط التركيب

جزء من قسم Object Oriented Programming في رحلة C# على Coddy — الدرس 61 من 70.

إنّ نمط Composite هو نمط هيكلي يسمح لك بتركيب الكائنات في هياكل شجرية ومعاملة الكائنات الفردية ومجموعات الكائنات بشكل موحد. إنه مثالي لتمثيل التسلسلات الهرمية (جزء من كل) حيث ترغب في التعامل مع كل من العناصر الفردية والمجموعات من خلال الواجهة نفسها.

يتكون النمط من ثلاثة مكونات رئيسية: واجهة مكون (component interface) تحدد العمليات المشتركة، وفئات طرفية (leaf classes) تمثل الكائنات الفردية، وفئات مركبة (composite classes) تحتوي على عناصر تابعة وتنفذ نفس الواجهة:

public interface IFileSystemItem
{
    string Name { get; }
    int GetSize();
}

public class File : IFileSystemItem
{
    public string Name { get; }
    private int _size;
    
    public File(string name, int size)
    {
        Name = name;
        _size = size;
    }
    
    public int GetSize() => _size;
}

public class Folder : IFileSystemItem
{
    public string Name { get; }
    private List<IFileSystemItem> _items = new List<IFileSystemItem>();
    
    public Folder(string name) => Name = name;
    
    public void Add(IFileSystemItem item) => _items.Add(item);
    
    public int GetSize() => _items.Sum(item => item.GetSize());
}

جمال هذا النمط هو أن العملاء لا يحتاجون إلى معرفة ما إذا كانوا يعملون مع ملف واحد أو تسلسل هرمي كامل للمجلدات:

var root = new Folder("root");
root.Add(new File("doc.txt", 100));

var subFolder = new Folder("images");
subFolder.Add(new File("photo.jpg", 500));
subFolder.Add(new File("icon.png", 50));

root.Add(subFolder);

Console.WriteLine(root.GetSize());  // 650

يعد نمط الـ Composite مثاليًا لأنظمة الملفات، أو أشجار مكونات واجهة المستخدم، أو المخططات التنظيمية، أو أي هيكل يجب فيه التعامل مع الحاويات والمحتويات بنفس الطريقة. فهو يبسط كود العميل (client code) من خلال القضاء على الحاجة للتمييز بين العناصر البسيطة والمعقدة.

challenge icon

التحدي

سهل

لنقم ببناء نظام للهيكل التنظيمي باستخدام نمط Composite. ستقوم بإنشاء هيكل حيث يمكن التعامل مع الموظفين الأفراد والأقسام (التي تحتوي على موظفين أو أقسام أخرى) بشكل موحد، مما يسمح لك بحساب إجمالي تكلفة الرواتب عند أي مستوى في التسلسل الهرمي.

ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:

  • OrgComponent.cs: قم بتعريف واجهة IOrgComponent في مساحة الأسماء Organization مع عضوين: خاصية Name (سلسلة نصية) وطريقة GetSalary() التي تعيد عدداً صحيحاً. تمثل هذه الواجهة العقد المشترك الذي سيلتزم به كل من الموظفين الأفراد والأقسام.
  • OrgClasses.cs: قم بإنشاء فئتين في نفس مساحة الأسماء:
    • Employee - فئة طرفية (leaf) تمثل عاملاً فردياً. تأخذ الاسم والراتب في منشئها (constructor) وتنفذ الواجهة عن طريق إرجاع راتبها الخاص.
    • Department - فئة مركبة (composite) تمثل مجموعة. تأخذ اسماً في منشئها وتحتفظ بقائمة من عناصر IOrgComponent. قم بتضمين طريقة Add(IOrgComponent component) لإضافة الأعضاء. يجب أن تعيد GetSalary() الخاصة بها مجموع رواتب جميع أعضائها.
  • Program.cs: قم ببناء هيكل تنظيمي بناءً على المدخلات واحسب إجمالي الراتب. ستقوم بإنشاء أقسام وموظفين، ودمجهم بشكل مناسب، وتوضيح كيف يسمح لك نمط composite بالتعامل مع التسلسل الهرمي بأكمله بشكل موحد.

ستتلقى المدخلات التالية:

  • عدد المكونات المراد إنشاؤها
  • لكل مكون: النوع (employee أو department)، يليه الاسم، وبالنسبة للموظفين، راتبهم. بالنسبة للأقسام، ستتلقى أيضاً اسم القسم الأب (أو root إذا كان قسماً في المستوى الأعلى)

تنسيق الإدخال لكل مكون:

employee
{name}
{salary}
{parent_department}

department
{name}
{parent_department}

بعد بناء الهيكل، اطبع إجمالي راتب القسم الجذري (root) بالتنسيق {root_name} Total Salary: {amount}.

على سبيل المثال، إذا كانت المدخلات هي:

5
department
Engineering
root
employee
Alice
5000
Engineering
employee
Bob
4500
Engineering
department
QA
Engineering
employee
Charlie
4000
QA

يجب أن يكون الناتج:

Engineering Total Salary: 13500

يحتوي قسم Engineering على Alice (5000)، و Bob (4500)، وقسم QA. يحتوي QA على Charlie (4000). عندما تستدعي GetSalary() على Engineering، فإنها تحسب الإجمالي بشكل متكرر (recursively) من جميع المكونات المتداخلة - مما يوضح كيف يتيح لك نمط Composite العمل مع هياكل شجرية معقدة من خلال واجهة بسيطة وموحدة!

ورقة مرجعية

نمط Composite (أو النمط التركيبي) هو نمط هيكلي يقوم بتكوين الكائنات في هياكل شجرية ويعامل الكائنات الفردية والمجموعات بشكل موحد من خلال نفس الواجهة. إنه مثالي للتسلسلات الهرمية من نوع "جزء-من-كل".

يتكون هذا النمط من ثلاثة مكونات رئيسية:

  • واجهة المكون (Component interface): تحدد العمليات المشتركة
  • فئات الأوراق (Leaf classes): تمثل الكائنات الفردية
  • الفئات المركبة (Composite classes): تحتوي على أبناء وتنفذ نفس الواجهة

مثال على التنفيذ الأساسي:

// واجهة المكون
public interface IFileSystemItem
{
    string Name { get; }
    int GetSize();
}

// فئة الورقة
public class File : IFileSystemItem
{
    public string Name { get; }
    private int _size;
    
    public File(string name, int size)
    {
        Name = name;
        _size = size;
    }
    
    public int GetSize() => _size;
}

// الفئة المركبة
public class Folder : IFileSystemItem
{
    public string Name { get; }
    private List<IFileSystemItem> _items = new List<IFileSystemItem>();
    
    public Folder(string name) => Name = name;
    
    public void Add(IFileSystemItem item) => _items.Add(item);
    
    public int GetSize() => _items.Sum(item => item.GetSize());
}

الاستخدام - يعمل العملاء مع العناصر الفردية والمجموعات بشكل موحد:

var root = new Folder("root");
root.Add(new File("doc.txt", 100));

var subFolder = new Folder("images");
subFolder.Add(new File("photo.jpg", 500));
subFolder.Add(new File("icon.png", 50));

root.Add(subFolder);

Console.WriteLine(root.GetSize());  // 650

نمط Composite مفيد لأنظمة الملفات، أو أشجار مكونات واجهة المستخدم، أو المخططات التنظيمية، أو أي هيكل يجب فيه معاملة الحاويات والمحتويات بنفس الطريقة.

جرّب بنفسك

using System;
using System.Collections.Generic;
using Organization;

class Program
{
    public static void Main(string[] args)
    {
        int n = Convert.ToInt32(Console.ReadLine());
        
        // TODO: قم بإنشاء قاموس (dictionary) لتخزين الأقسام حسب الاسم
        // سيساعدك هذا في العثور على الأقسام الأب عند إضافة المكونات
        Dictionary<string, Department> departments = new Dictionary<string, Department>();
        
        // TODO: تتبع القسم الرئيسي (root)
        Department root = null;
        
        for (int i = 0; i < n; i++)
        {
            string type = Console.ReadLine();
            
            if (type == "department")
            {
                string name = Console.ReadLine();
                string parent = Console.ReadLine();
                
                // TODO: قم بإنشاء القسم
                // TODO: أضفه إلى قاموس الأقسام departments
                // TODO: إذا كان الأب هو "root"، فهذا هو القسم الرئيسي
                // TODO: بخلاف ذلك، ابحث عن القسم الأب وأضف هذا القسم إليه
            }
            else if (type == "employee")
            {
                string name = Console.ReadLine();
                int salary = Convert.ToInt32(Console.ReadLine());
                string parent = Console.ReadLine();
                
                // TODO: قم بإنشاء الموظف
                // TODO: ابحث عن القسم الأب وأضف الموظف إليه
            }
        }
        
        // TODO: اطبع إجمالي الرواتب للقسم الرئيسي
        // التنسيق: "{root_name} Total Salary: {amount}"
        Console.WriteLine($"{root.Name} Total Salary: {root.GetSalary()}");
    }
}
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming