Menu
Coddy logo textTech

תבנית ההרכבה

חלק מהיחידה תכנות מונחה עצמים במסלול ה-C# של Coddy. שיעור 61 מתוך 70.

תבנית Composite היא תבנית מבנית שמאפשרת להרכיב אובייקטים למבני עץ ולהתייחס לאובייקטים בודדים ולקבוצות של אובייקטים באופן אחיד. היא מתאימה במיוחד לייצוג היררכיות של שלם וחלק, שבהן רוצים לעבוד גם עם פריטים בודדים וגם עם אוספים באמצעות אותו ממשק.

לתבנית יש שלושה רכיבים מרכזיים: ממשק רכיב שמגדיר פעולות משותפות, מחלקות עלים שמייצגות אובייקטים בודדים, ומחלקות מורכבות שמכילות צאצאים ומממשות את אותו ממשק:

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
challenge icon

אתגר

קל

בואו נבנה מערכת תרשים ארגוני באמצעות תבנית Composite. תיצרו מבנה שבו אפשר להתייחס באופן אחיד גם לעובדים יחידים וגם למחלקות (המכילות עובדים או מחלקות אחרות), וכך לחשב את עלות השכר הכוללת בכל רמה בהיררכיה.

תארגנו את הקוד בשלושה קבצים:

  • OrgComponent.cs: הגדירו ממשק IOrgComponent במרחב השמות Organization עם שני איברים: מאפיין Name (מחרוזת) ומתודה GetSalary() שמחזירה מספר שלם. הממשק הזה מייצג את החוזה המשותף שגם עובדים יחידים וגם מחלקות יממשו.
  • OrgClasses.cs: צרו שתי מחלקות באותו מרחב שמות:
    • Employee - מחלקת עלה שמייצגת עובד יחיד. היא מקבלת שם ושכר בבנאי שלה ומממשת את הממשק על ידי החזרת השכר שלה.
    • Department - מחלקה מורכבת שמייצגת קבוצה. היא מקבלת שם בבנאי שלה ומנהלת רשימה של פריטים מסוג IOrgComponent. הוסיפו מתודה Add(IOrgComponent component) כדי להוסיף חברים. המתודה GetSalary() שלה צריכה להחזיר את סכום המשכורות של כל חבריה.
  • Program.cs: בנו מבנה ארגוני על סמך הקלט וחשבו את השכר הכולל. תיצרו מחלקות ועובדים, תקננו אותם בהתאם, ותדגימו כיצד תבנית Composite מאפשרת להתייחס לכל ההיררכיה באופן אחיד.

תקבלו את הקלט הבא:

  • מספר הרכיבים שיש ליצור
  • עבור כל רכיב: הסוג (employee או department), ואחריו השם, ועבור עובדים גם השכר שלהם. עבור מחלקות תקבלו גם את שם מחלקת האב (או root אם זו מחלקה ברמה העליונה)

פורמט הקלט עבור כל רכיב:

employee
{name}
{salary}
{parent_department}

department
{name}
{parent_department}

לאחר בניית המבנה, הדפיסו את השכר הכולל של מחלקת השורש בפורמט {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, היא מחשבת באופן רקורסיבי את הסכום מכל הרכיבים המקוננים — וכך מדגימה כיצד תבנית 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<string, Department> departments = new Dictionary<string, Department>();
        
        // TODO: עקבו אחר מחלקת השורש
        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: הוסיפו אותה למילון המחלקות
                // TODO: אם parent הוא "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בחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר C# אונליין