Menu
Coddy logo textTech

תבנית המורכב

חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 101 מתוך 104.

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

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

#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. תיצרו היררכיה שבה ניתן להתייחס באופן אחיד הן לעובדים יחידים והן למחלקות (המכילות עובדים אחרים או תת-מחלקות). הדבר משקף את המבנה של חברות אמיתיות: מחלקות מכילות אנשים ומחלקות אחרות, ויוצרות מבנה של עץ.

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

  • OrgComponent.h: הגדירו את ממשק הרכיב שגם עובדים וגם מחלקות יממשו.

    צרו מחלקה מופשטת OrgComponent עם:

    • getName(): מחזירה את שם הרכיב
    • getSalary(): מחזירה את השכר הכולל (עבור עובדים, את השכר שלהם; עבור מחלקות, את סכום כל המשכורות הכלולות בהן)
    • display(int indent = 0): מציגה את הרכיב עם הזחה מתאימה

    כללו מפרק וירטואלי.

  • Organization.h: ממשו את המחלקות עלה ומורכב.

    צרו מחלקה Employee (עלה) ששומרת שם ושכר. המתודה display() שלה צריכה להדפיס את פרטי העובד בפורמט הזה:

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

    צרו מחלקה Department (מורכב) ששומרת שם ואוסף של רכיבי OrgComponent ילדים באמצעות std::shared_ptr. ממשו:

    • add(std::shared_ptr<OrgComponent> component): מוסיפה ילד למחלקה
    • getSalary(): מחשבת באופן רקורסיבי את השכר הכולל של כל החברים
    • display(): מדפיסה את שם המחלקה בתוך סוגריים מרובעים, ואז מציגה את כל הילדים עם הזחה מוגדלת (הוסיפו 2 רווחים בכל רמה)

    פורמט התצוגה של המחלקה צריך להיות:

    [indent spaces][Department Name]
      [children displayed with indent + 2]
  • main.cpp: בנו והציגו מבנה ארגוני.

    קראו ארבעה ערכים:

    1. שם החברה (מחרוזת)
    2. שם המחלקה (מחרוזת)
    3. שם העובד הראשון והשכר שלו (פורמט: name,salary)
    4. שם העובד השני והשכר שלו (פורמט: name,salary)

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

    לאחר הצגת המבנה, הדפיסו:

    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() פועלת באופן אחיד בין אם קוראים לה עבור עובד ובין אם עבור מחלקה: המחלקה מצרפת אוטומטית את המשכורות של כל חבריה. קוד הלקוח לא צריך להבחין בין עובדים יחידים לבין מחלקות שלמות בעת חישוב הסכומים או הצגת ההיררכיה.

נסו בעצמכם

#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);
    
    // פונקציית למבדא עזר לפענוח הפורמט "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++ אונליין