מבוא לתבניות עיצוב
חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 85 מתוך 107.
תבניות עיצוב הן פתרונות לשימוש חוזר לבעיות נפוצות שמתעוררות בתכנון תוכנה. הן אינן קוד שמעתיקים ישירות, אלא תבניות שמנחות אתכם כיצד לבנות את הטיפוסים שלכם ואת האינטראקציות ביניהם כדי לפתור אתגרים ספציפיים.
דפוסי עיצוב מקובצים בדרך כלל בשלוש קטגוריות:
| קטגוריה | מטרה | דוגמאות |
|---|---|---|
| יצירתיים | מנגנונים ליצירת אובייקטים | Singleton, Factory, Builder |
| מבניים | הרכבת טיפוסים למבנים גדולים יותר | Adapter, Decorator, Composite |
| התנהגותיים | תקשורת בין אובייקטים | Observer, Strategy, Command |
ב-Go, דפוסי עיצוב נראים אחרת מאשר בשפות OOP מסורתיות.
בלי מחלקות או ירושה, Go מממשת תבניות באמצעות ממשקים, הטמעת מבנים והרכבה. למשל, במקום שבו Java עשויה להשתמש במחלקה מופשטת, Go משתמשת בממשק. במקום שבו C++ עשויה להשתמש בירושה, Go משתמשת בהטמעה.
למידת דפוסים עוזרת לך לזהות בעיות נפוצות ולתקשר פתרונות ביעילות עם מפתחים אחרים. כשמישהו אומר "השתמשו כאן בדפוס Strategy", כל הצוות מבין את הגישה בלי צורך בהסברים ארוכים.
בשיעורים הבאים תממשו כמה דפוסים בסיסיים ב-Go, החל בדפוסים יצירתיים כמו Singleton ו-Factory, ולאחר מכן תעברו לדפוסים התנהגותיים כמו Observer ו-Strategy.
אתגר
קלבואו נבנה מערכת התראות פשוטה שמדגימה כיצד דפוסי תכנון עוזרים לארגן קוד! תיצרו מבנה שאפשר יהיה להרחיב בקלות באמצעות דפוסים כמו Observer או Strategy בשיעורים עתידיים, אבל לעת עתה, התמקדו בהגדרת ממשקים וטיפוסים ברורים שפועלים לפי עקרונות OOP טובים.
תארגנו את הקוד בשלושה קבצים:
notifier.go: הגדירו את הממשק המרכזי ואת הטיפוסים עבור מערכת ההתראות שלכם.צרו ממשק
Notifierעם מתודה יחידהSend(message string) stringשמחזירה הודעת אישור.ממשו שני טיפוסי התראות קונקרטיים:
EmailNotifierעם שדהAddress. המתודהSendשלו מחזירהEmail to [address]: [message]SMSNotifierעם שדהPhoneNumber. המתודהSendשלו מחזירהSMS to [phone]: [message]
dispatcher.go: צרו מפיץ שפועל עם כל notifier באמצעות הממשק.בנו מבנה
NotificationDispatcherשמכיל פרוסה של ממשקיNotifier. הוסיפו את המתודות הבאות:AddNotifier(n Notifier)כדי לרשום notifierBroadcast(message string) []stringששולחת את ההודעה באמצעות כל ה-notifiers הרשומים ומחזירה פרוסה של כל מחרוזות האישור
צרו גם בנאי
NewNotificationDispatcher()שמחזיר מפיץ מאותחל.main.go: הגדירו notifiers ושדרו הודעות.קראו את מספר ה-notifiers ליצירה. עבור כל notifier, קראו את הטיפוס שלו (
emailאוsms) ואת היעד שלו (כתובת או מספר טלפון). הוסיפו כל אחד למפיץ.לאחר מכן, קראו הודעה לשידור. שלחו אותה באמצעות כל ה-notifiers והדפיסו כל אישור בשורה נפרדת.
הקלט הבא יסופק:
- שורה 1: מספר ה-notifiers
- עבור כל notifier: הטיפוס (
emailאוsms), ולאחר מכן היעד - השורה האחרונה: הודעה לשידור
לדוגמה, בהינתן:
3
email
alice@example.com
sms
555-1234
email
bob@example.com
Server maintenance at midnightהפלט שלכם צריך להיות:
Email to alice@example.com: Server maintenance at midnight
SMS to 555-1234: Server maintenance at midnight
Email to bob@example.com: Server maintenance at midnightובהינתן:
2
sms
555-9999
sms
555-0000
Meeting canceledהפלט שלכם צריך להיות:
SMS to 555-9999: Meeting canceled
SMS to 555-0000: Meeting canceledהמבנה הזה מדגים כיצד ממשקים מאפשרים תכנונים גמישים וניתנים להרחבה. המפיץ לא מכיר את טיפוסי ההתראות הספציפיים. הוא רק יודע שהם יכולים לבצע Send. זהו הבסיס שעליו נשענים דפוסים כמו Observer ו-Strategy!
נסו בעצמכם
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// קרא את מספר אמצעי ההתראה
countStr, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countStr))
// צור מפיץ חדש
dispatcher := NewNotificationDispatcher()
// קרא כל אמצעי התראה והוסף אותו למפיץ
for i := 0; i < count; i++ {
notifierType, _ := reader.ReadString('\n')
notifierType = strings.TrimSpace(notifierType)
destination, _ := reader.ReadString('\n')
destination = strings.TrimSpace(destination)
// TODO: צור את אמצעי ההתראה המתאים בהתאם לסוג
// והוסף אותו למפיץ
// רמז: בדוק אם notifierType הוא "email" או "sms"
}
// קרא את ההודעה להפצה
message, _ := reader.ReadString('\n')
message = strings.TrimSpace(message)
// TODO: הפץ את ההודעה והדפס כל אישור
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות OOP ב-Go
קבצים חיצונייםסביבת העבודה והמודולים של Goחבילות וייבואשמות מיוצאים לעומת שמות לא מיוצאיםמבוא ל-OOP ב-GoStructs כמחלקותהגדרת מתודות עבור Structsמקבלי Pointer לעומת Valueאתחול Structפונקציות Constructorחזרה – מחשבון פשוט4ממשקים
מבוא לממשקיםמימוש משתמעממשק כחוזהממשק ריק (any)אימות טיפוסמתג טיפוסיםהרכבת ממשקיםממשקי Stringer ו-Errorסיכום – מחשבון צורות7כימוס
שדות מיוצאים לעומת שדות לא מיוצאיםכימוס ברמת החבילהמתודות Getter ו-Setterהסתרת מידע ב-Goסיכום – רשומות תלמידים10Generics (Go 1.18 ומעלה)
מבוא ל־Genericsפרמטרים של טיפוסיםאילוצי טיפוסיםמבנים גנרייםפתרון עוקף למתודות גנריותסיכום – אוסף גנרי13תבניות עיצוב חלק 1
מבוא לתבניות עיצובתבנית Singletonתבנית Factoryתבנית Abstract Factoryתבנית Observerתבנית Strategy2העמקה בסוגים וב-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 אונליין