תבנית העיצוב
חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 93 מתוך 107.
דפוס Decorator מאפשר לך להוסיף התנהגות חדשה לאובייקטים באופן דינמי על ידי עטיפתם באובייקטים אחרים. בעוד ש-Adapter משנה ממשק כדי שיתאים לממשק אחר, Decorator משאיר את אותו ממשק אך משפר את הפונקציונליות באמצעות הוספת שכבות של עטיפות סביב האובייקט המקורי.
ב-Go, אנחנו מממשים זאת בכך שגם הטיפוס הבסיסי וגם העוטפים מממשים את אותו ממשק:
type Notifier interface {
Send(message string) string
}
type BasicNotifier struct{}
func (b BasicNotifier) Send(message string) string {
return "Sending: " + message
}עוטפים Notifier נוסף בעטיפות ומוסיפים התנהגות לפני העברת הביצוע אליו או אחריה:
type TimestampDecorator struct {
wrapped Notifier
}
func (t TimestampDecorator) Send(message string) string {
timestamped := "[2024-01-15] " + message
return t.wrapped.Send(timestamped)
}
type UppercaseDecorator struct {
wrapped Notifier
}
func (u UppercaseDecorator) Send(message string) string {
return strings.ToUpper(u.wrapped.Send(message))
}העוצמה של Decorator טמונה בערימת כמה עטיפות כדי לשלב התנהגויות:
notifier := BasicNotifier{}
withTimestamp := TimestampDecorator{wrapped: notifier}
withBoth := UppercaseDecorator{wrapped: withTimestamp}
fmt.Println(notifier.Send("Hello"))
// שולח: Hello
fmt.Println(withBoth.Send("Hello"))
// שולח: [2024-01-15] HELLODecorator מתאים במיוחד כשצריך להוסיף אחריות לאובייקטים בלי לשנות את הקוד שלהם, במיוחד כשנדרשים שילובים שונים של תכונות. משתמשים בו לעיתים קרובות בשכבות של רישום ביומן, מטמון, אימות ודחיסה.
אתגר
קלבואו נבנה מערכת הזמנות לבית קפה באמצעות תבנית העיצוב Decorator! תיצרו משקה קפה בסיסי ומקשטים שמוסיפים תוספות כמו חלב, סוכר וקצפת. כל מקשט עוטף את הקודם, ובונה בהדרגה גם את התיאור וגם את המחיר: דוגמה מצוינת מהעולם האמיתי לאופן שבו מקשטים מוסיפים שכבות של פונקציונליות.
תארגנו את הקוד שלכם בשלושה קבצים:
beverage.go: הגדירו את הממשק המרכזי ואת סוג הקפה הבסיסי שלכם.צרו ממשק
Beverageעם שתי מתודות:Description() string: מחזירה מהו המשקהCost() float64: מחזירה את המחיר
ממשו מבנה
Coffeeבתור המשקה הבסיסי שלכם. התיאור שלו צריך להיותCoffeeוהמחיר שלו צריך להיות2.00.decorators.go: צרו את סוגי המקשטים שמוסיפים תוספות לכל משקה.כל מקשט עוטף
Beverageומממש את אותו הממשק. צרו שלושה מקשטים:MilkDecorator: מוסיף, Milkלתיאור ו-0.50למחירSugarDecorator: מוסיף, Sugarלתיאור ו-0.25למחירWhipDecorator: מוסיף, Whipלתיאור ו-0.75למחיר
כל מקשט צריך לקרוא למתודות של המשקה העטוף ולשפר את התוצאה.
main.go: בנו משקאות בהתאמה אישית באמצעות ערימה של מקשטים.קראו את מספר התוספות שיש להוסיף. לאחר מכן, עבור כל תוספת, קראו את הסוג שלה (
milk,sugarאוwhip) ועטפו את המשקה הנוכחי שלכם במקשט המתאים.לאחר החלת כל התוספות, הדפיסו את תיאור המשקה הסופי ואת מחירו בשורות נפרדות. עצבו את המחיר בתבנית
$X.XXעם שתי ספרות אחרי הנקודה העשרונית בדיוק.
הקלט הבא יסופק:
- שורה 1: מספר התוספות שיש להוסיף
- השורות הבאות: סוג תוספת אחד בכל שורה (
milk,sugarאוwhip)
לדוגמה, בהינתן:
2
milk
sugarהפלט שלכם צריך להיות:
Coffee, Milk, Sugar
$2.75ובהינתן:
3
whip
milk
milkהפלט שלכם צריך להיות:
Coffee, Whip, Milk, Milk
$3.75ובהינתן:
0הפלט שלכם צריך להיות:
Coffee
$2.00שימו לב איך כל מקשט עוטף את המשקה הקודם, ובונה בהדרגה גם את התיאור וגם את המחיר. אפשר להוסיף את אותה תוספת כמה פעמים (כמו מנה כפולה של חלב), וסדר המקשטים קובע את סדר התוספות בתיאור. הקפה הבסיסי אינו יודע דבר על התוספות: כל מקשט פשוט משפר את מה שהוא עוטף!
נסו בעצמכם
package main
import (
"fmt"
)
func main() {
// קראו את מספר התוספות
var count int
fmt.Scanln(&count)
// התחילו עם קפה בסיסי
var drink Beverage = &Coffee{}
// קראו כל תוספת ועטפו את המשקה בדקורטור המתאים
for i := 0; i < count; i++ {
var extra string
fmt.Scanln(&extra)
// TODO: בהתאם לסוג התוספת, עטפו את drink בדקורטור המתאים
// השתמשו ב-MilkDecorator עבור "milk"
// השתמשו ב-SugarDecorator עבור "sugar"
// השתמשו ב-WhipDecorator עבור "whip"
}
// TODO: הדפיסו את התיאור והעלות הסופיים
// עצבו את העלות בתבנית $X.XX עם שתי ספרות בדיוק אחרי הנקודה העשרונית
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
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 API14תבניות עיצוב חלק 2
תבנית הפקודהתבנית המתאםתבנית העיצובתבנית שיטת התבניתתבנית המצבתבנית המורכבMiddleware בתור Decorator3מצביעים וזיכרון
יסודות המצביעים ב-Goמצביעים למבניםהעברה לפי ערך לעומת לפי הפניההפונקציה new()איסוף אשפה ב-Goסיכום - בונה רשימה מקושרת6פולימורפיזם ב-Go
פולימורפיזם באמצעות ממשקיםטיפוס ברווז ב-Goכללי התאמה לממשקאוספים פולימורפייםהזרקת תלויותסיכום - מעבד תשלומים9מקביליות ו-OOP
יסודות GoroutinesChannels ותקשורתChannel עם חוצץ לעומת ללא חוצץפקודת Selectsync.Mutex ו-sync.RWMutexsync.WaitGroupתכנון Struct בטוח לשימוש במקבילסיכום - מאגר עובדים12מושגים מתקדמים בתכנות מונחה עצמים
דפוס האפשרויות הפונקציונליותדפוס Builder ב-Goשרשור מתודותכינויי טיפוסים לעומת הגדרותיסודות Reflectionסקירה כללית של יצירת קודתרגלו בעצמכם: קומפיילר Go אונליין