Menu
Coddy logo textTech

תבנית המורכב

חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 96 מתוך 107.

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

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

type Component interface {
    GetSize() int
}

type File struct {
    Name string
    Size int
}

func (f File) GetSize() int {
    return f.Size
}

המורכב (המכיל) מחזיק צאצאים ומממש את אותו ממשק באמצעות צבירת התוצאות שלהם:

type Folder struct {
    Name     string
    Children []Component
}

func (f *Folder) Add(c Component) {
    f.Children = append(f.Children, c)
}

func (f Folder) GetSize() int {
    total := 0
    for _, child := range f.Children {
        total += child.GetSize()
    }
    return total
}

עכשיו אפשר לקנן תיקיות בתוך תיקיות, והקריאה ל־GetSize() פועלת באותו אופן, בין אם מדובר בקובץ יחיד ובין אם בעץ תיקיות שלם:

docs := &Folder{Name: "docs"}
docs.Add(File{Name: "readme.txt", Size: 100})
docs.Add(File{Name: "notes.txt", Size: 50})

root := &Folder{Name: "root"}
root.Add(docs)
root.Add(File{Name: "config.json", Size: 25})

fmt.Println(root.GetSize())  // 175

דפוס Composite אידיאלי כשצריך לייצג היררכיות של שלם וחלקיו ורוצים שהלקוחות יתייחסו לאובייקטים בודדים ולהרכבות באופן זהה.

challenge icon

אתגר

קל

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

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

  • component.go: הגדירו את הממשק המשותף שגם יחידים וגם קבוצות יממשו.

    צרו ממשק OrgComponent עם שתי מתודות:

    • GetSalary() int: מחזירה את סך המשכורת של הרכיב הזה
    • GetName() string: מחזירה את שם העובד או המחלקה
  • organization.go: ממשו את הטיפוסים של העלה (עובד יחיד) ושל המורכב (מחלקה).

    צרו מבנה Employee עם השדות Name (מחרוזת) ו-Salary (מספר שלם). המתודה GetSalary() שלו מחזירה את המשכורת שלו, והמתודה GetName() מחזירה את שמו.

    צרו מבנה Department עם שדה Name (מחרוזת) ופרוסת Members שמכילה פריטים מסוג OrgComponent. הוסיפו מתודה Add(c OrgComponent) כדי להוסיף חברים. המתודה GetSalary() שלו צריכה להחזיר את סכום המשכורות של כל החברים, והמתודה GetName() מחזירה את שם המחלקה.

  • main.go: בנו מבנה ארגוני וחשבו משכורות.

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

    לאחר בניית המבנה, קראו אינדקס סופי והדפיסו את שם הרכיב הזה ואת סך המשכורת שלו בפורמט: [name]: [salary]

הקלטים הבאים יסופקו:

  • מספר העובדים, ולאחר מכן השם והמשכורת של כל עובד בשורות נפרדות
  • מספר המחלקות, ולאחר מכן שם כל מחלקה, מספר החברים והאינדקסים שלהם
  • האינדקס הסופי לבדיקה

לדוגמה, עבור הקלט:

3
Alice
50000
Bob
60000
Carol
55000
1
Engineering
3
0
1
2
3

הפלט שלכם צריך להיות:

Engineering: 165000

ועבור הקלט:

4
Alice
50000
Bob
60000
Carol
55000
Dave
70000
2
Dev
2
0
1
QA
2
2
3
4

הפלט שלכם צריך להיות:

QA: 125000

ועבור הקלט:

2
Alice
50000
Bob
60000
0
1

הפלט שלכם צריך להיות:

Bob: 60000

שימו לב ש-GetSalary() פועלת באופן זהה בין אם קוראים לה על עובד יחיד ובין אם על מחלקה שלמה: תבנית Composite מאפשרת להתייחס ליחידים ולקבוצות באופן אחיד באמצעות אותו ממשק!

נסו בעצמכם

package main

import "fmt"

func main() {
	// קרא את מספר העובדים
	var numEmployees int
	fmt.Scanln(&numEmployees)

	// אחסן את כל הרכיבים (עובדים ומחלקות) לפי סדר יצירתם
	var components []OrgComponent

	// TODO: קרא את השם והשכר של כל עובד
	// צור מופעים של Employee והוסף אותם לפרוסה components
	for i := 0; i < numEmployees; i++ {
		var name string
		var salary int
		fmt.Scanln(&name)
		fmt.Scanln(&salary)
		// TODO: צור עובד והוסף אותו ל-components
	}

	// קרא את מספר המחלקות
	var numDepartments int
	fmt.Scanln(&numDepartments)

	// TODO: קרא את השם, מספר החברים והאינדקסים של החברים בכל מחלקה
	// צור מופעים של Department, הוסף חברים לפי אינדקס והוסף אותם ל-components
	for i := 0; i < numDepartments; i++ {
		var deptName string
		var memberCount int
		fmt.Scanln(&deptName)
		fmt.Scanln(&memberCount)
		// TODO: צור מחלקה, קרא את האינדקסים של החברים, הוסף חברים והוסף אותה ל-components
	}

	// קרא את האינדקס האחרון לשאילתה
	var queryIndex int
	fmt.Scanln(&queryIndex)

	// TODO: הדפס את שם הרכיב ואת השכר הכולל בפורמט: [name]: [salary]
	// דוגמה: fmt.Printf("%s: %d\n", name, salary)
}
quiz iconבחנו את עצמכם

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

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

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