הטמעה לעומת אגרגציה
חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 38 מתוך 107.
למדת שהטמעה מקדמת שדות ומתודות אל המבנה החיצוני. אבל Go תומכת גם בצבירה, שבה כוללים מבנה אחר כשדה בעל שם. הבנת מתי להשתמש בכל גישה היא מפתח לתכנון טוב.
בהטמעה, משמיטים את שם השדה, והחברים של הטיפוס הפנימי הופכים לנגישים ישירות:
type Writer struct{}
func (w Writer) Write() string { return "writing" }
type Document struct {
Writer // הטמעה - ללא שם שדה
}
d := Document{}
d.Write() // גישה ישירה
בעזרת אגרגציה, נותנים לשדה שם מפורש, ולכן צריך להשתמש בשם הזה כדי לגשת לטיפוס הפנימי:
type Document struct {
writer Writer // אגרגציה - שדה בעל שם
}
d := Document{writer: Writer{}}
d.writer.Write() // יש להשתמש בשם השדה
ההבדל העיקרי הוא הקשר שאתם מביעים. הכלה מרמזת על קשר מסוג "הוא-סוג-של", שבו הטיפוס החיצוני מקבל את הזהות של הטיפוס הפנימי. צבירה מרמזת על קשר מסוג "יש-לו", שבו הטיפוס החיצוני רק מכיל את הטיפוס הפנימי.
השתמשו בהטמעה כשרוצים שהטיפוס החיצוני יממש ממשקים שהטיפוס הפנימי מממש, או כשגישה ישירה למתודות הופכת את ה-API לנקי יותר. השתמשו בצבירה כשהטיפוס הפנימי הוא פרט מימוש שלא אמור להיחשף, או כשרוצים שליטה מפורשת באופן הגישה לטיפוס הפנימי.
אתגר
קלבואו נבנה מערכת ליצירת דוחות שמדגימה מתי להשתמש בהטמעה לעומת צבירה. תיצור מערכת שבה רכיבים מסוימים יהיו נגישים ישירות (הטמעה), בעוד שאחרים יישארו פרטי מימוש פנימיים (צבירה).
תארגן את הקוד שלך בשלושה קבצים:
formatter.go: צור מבנהFormatterעם שדהStyle(מחרוזת) ומתודהFormat(text string) stringשמחזירה את הטקסט כשהוא עטוף בסגנון, כמו[Style] text [/Style]. זה יהיה כלי עזר פנימי שלא אמור להיות חשוף ישירות.report.go: צור שני מבנים שמטפלים ביצירת דוחות בדרכים שונות:- מבנה
Metadataעם השדותAuthorו-Date. הוא מייצג מידע ציבורי שאמור להיות נגיש ישירות - מבנה
Reportעם שדהTitleשמטמיע אתMetadata(כדי לאפשר גישה ישירה למחבר ולתאריך), אך משתמש בצבירה עבורFormatter(כפרט מימוש פנימי עם שדה בעל שםformatter)
ReportמתודהGenerate() stringשמשתמשת במעצב הפנימי כדי לעצב את הכותרת ולכלול את המטא-נתונים.- מבנה
main.go: קרא את פרטי הדוח מהקלט, וצורReportעם מטא-נתונים מוטמעים וגם מעצב מצטבר. הדגם את ההבדל באמצעות גישה ישירה למחבר מתוך הדוח (דרך ההטמעה), ולאחר מכן יצירת הדוח המעוצב (שמשתמש במעצב המוסתר באופן פנימי).
הקלטים הבאים יסופקו:
- שורה 1: כותרת הדוח
- שורה 2: שם המחבר
- שורה 3: תאריך
- שורה 4: סגנון העיצוב
המתודה Format שלך צריכה להחזיר:
[Style] text [/Style]המתודה Generate שלך צריכה להחזיר:
[formatted title]
Author: [Author], Date: [Date]בקובץ הראשי שלך, הדפס שלוש שורות:
- המחבר שאליו ניגשים ישירות מתוך Report (כדי להדגים שההטמעה מקדמת את השדה)
- התאריך שאליו ניגשים דרך שם הטיפוס המוטמע (
report.Metadata.Date) - התוצאה של קריאה ל-
Generate()
לדוגמה, עבור Quarterly Results, Jane Doe, 2024-01-15 ו-BOLD, הפלט שלך אמור להיות:
Jane Doe
2024-01-15
[BOLD] Quarterly Results [/BOLD]
Author: Jane Doe, Date: 2024-01-15שימו לב שאפשר לגשת ל-Author ול-Date ישירות מתוך Report באמצעות הטמעה, בעוד שהמעצב נשאר מוסתר. אי אפשר לקרוא ישירות ל-report.Format(), משום שהוא נצבר באמצעות שדה בעל שם. כך מודגמים בפועל היחסים "הוא-סוג-של" לעומת "יש-לו".
נסו בעצמכם
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
title := scanner.Text()
scanner.Scan()
author := scanner.Text()
scanner.Scan()
date := scanner.Text()
scanner.Scan()
style := scanner.Text()
// TODO: צרו struct מסוג Metadata עם המחבר והתאריך
// TODO: צרו Formatter עם הסגנון הנתון
// TODO: צרו Report שמטמיע את Metadata ומכיל את Formatter
// TODO: הדפיסו את המחבר באמצעות גישה ישירה דרך Report (ההטמעה מקדמת את השדה)
// TODO: הדפיסו את התאריך באמצעות גישה דרך שם הטיפוס המוטמע (report.Metadata.Date)
// TODO: הדפיסו את התוצאה של הקריאה ל־Generate()
// מנעו אזהרות על משתנים שאינם בשימוש (הסירו את השורות האלה כשתשתמשו במשתנים)
_ = title
_ = author
_ = date
_ = style
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות OOP ב-Go
קבצים חיצונייםסביבת העבודה והמודולים של Goחבילות וייבואשמות מיוצאים לעומת שמות לא מיוצאיםמבוא ל-OOP ב-GoStructs כמחלקותהגדרת מתודות עבור Structsמקבלי Pointer לעומת Valueאתחול Structפונקציות Constructorחזרה – מחשבון פשוט4ממשקים
מבוא לממשקיםמימוש משתמעממשק כחוזהממשק ריק (any)אימות טיפוסמתג טיפוסיםהרכבת ממשקיםממשקי Stringer ו-Errorסיכום – מחשבון צורות7כימוס
שדות מיוצאים לעומת שדות לא מיוצאיםכימוס ברמת החבילהמתודות Getter ו-Setterהסתרת מידע ב-Goסיכום – רשומות תלמידים10Generics (Go 1.18 ומעלה)
מבוא ל־Genericsפרמטרים של טיפוסיםאילוצי טיפוסיםמבנים גנרייםפתרון עוקף למתודות גנריותסיכום – אוסף גנרי2העמקה בסוגים וב-Structs
סוגים בסיסיים ומורכביםהגדרות של סוגים מותאמים אישיתתגיות StructStructs אנונימייםStructs מקונניםערכי אפס וברירות מחדלסיכום – ספר אנשי קשר5קומפוזיציה במקום ירושה
למה ב-Go אין ירושהיסודות הטמעת Structקידום מתודותהטמעת כמה Structsהטמעה לעומת אגרגציההסתרת מתודות מוטמעותסיכום – היררכיית עובדים8טיפול בשגיאות ו-OOP
ממשק errorסוגי שגיאות מותאמים אישיתעטיפת שגיאות (fmt.Errorf)שגיאות Sentinelerrors.Is() ו-errors.As()Panic, Defer ו-Recoverסיכום – מנתח קבצים11ספרייה סטנדרטית ותכנות מונחה עצמים
io.Reader ו-io.Writersort.Interfaceהממשק fmt.Stringerencoding/json עם מבניםהממשק http.Handlerסיכום – מודלים של REST API3מצביעים וזיכרון
יסודות המצביעים ב-Goמצביעים למבניםהעברה לפי ערך לעומת לפי הפניההפונקציה new()איסוף אשפה ב-Goסיכום - בונה רשימה מקושרת6פולימורפיזם ב-Go
פולימורפיזם באמצעות ממשקיםטיפוס ברווז ב-Goכללי התאמה לממשקאוספים פולימורפייםהזרקת תלויותסיכום - מעבד תשלומים9מקביליות ו-OOP
יסודות GoroutinesChannels ותקשורתChannel עם חוצץ לעומת ללא חוצץפקודת Selectsync.Mutex ו-sync.RWMutexsync.WaitGroupתכנון Struct בטוח לשימוש במקבילסיכום - מאגר עובדים12מושגים מתקדמים בתכנות מונחה עצמים
דפוס האפשרויות הפונקציונליותדפוס Builder ב-Goשרשור מתודותכינויי טיפוסים לעומת הגדרותיסודות Reflectionסקירה כללית של יצירת קודתרגלו בעצמכם: קומפיילר Go אונליין