תבנית ההרכבה
חלק מהיחידה תכנות מונחה עצמים במסלול ה-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אתגר
קלבואו נבנה מערכת תרשים ארגוני באמצעות תבנית 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()}");
}
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות התכנות מונחה העצמים
קבצים חיצונייםמרחבי שמות והנחיותמבוא למחלקות ולאובייקטיםמילת המפתח 'this'מתודות ופרמטריםשדות לעומת מאפייניםבנאיםאתחול אובייקטיםסיכום – מחשבון פשוט4ירושה
תחביר ירושה בסיסית (:)מילת המפתח 'base'מילות המפתח Virtual ו-Overrideמחלקות Sealedמחלקת הבסיס 'object'סיכום – היררכיית עובדים7תכונות מתקדמות
העמסת אופרטוריםאינדקסרים (this[])דריסת ToString()מתודות הרחבהסיכום – רשימה מותאמת אישית2מאפיינים וחברים סטטיים
מאפיינים במימוש אוטומטימאפיינים לקריאה בלבד ולכתיבה בלבדשדות ומתודות סטטייםמחלקות סטטיותחברים עם גוף ביטוי5פולימורפיזם וממשקים
פולימורפיזם בזמן קומפילציה לעומת בזמן ריצהממשק לעומת מחלקה מופשטתממשקים מרוביםממשקים מפורשיםהמרה כלפי מעלה והמרה כלפי מטהחזרה: מחשבון צורות8מושגים מתקדמים בתכנות מונחה עצמים
קומפוזיציה במקום ירושהGenerics (מחלקות ומתודות)נציגים ואירועיםמאפיינים ורפלקציהIDisposable והצהרת usingיסודות הזרקת תלויותתרגלו בעצמכם: קומפיילר C# אונליין