ממשק כחוזה
חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 27 מתוך 107.
התייחסות לממשקים כאל חוזים עוזרת לך לתכנן תוכניות Go טובות יותר. כשפונקציה מקבלת טיפוס ממשק, היא אומרת: "לא אכפת לי איזה טיפוס קונקרטי תיתן לי, כל עוד הוא עומד בחוזה הזה."
חשיבה מבוססת חוזים זו מאפשרת לך לכתוב פונקציות שמתמקדות בהתנהגות ולא בסוגים מסוימים. נבחן פונקציה שצריכה לעבד נתונים ממקורות שונים:
type DataSource interface {
FetchData() string
}
func ProcessData(src DataSource) {
data := src.FetchData()
fmt.Println("Processing:", data)
}לפונקציה ProcessData לא משנה אם היא מקבלת חיבור למסד נתונים, לקוח API או קורא קבצים. היא רק דורשת שהקלט יוכל לבצע FetchData(). כל טיפוס שמקיים את החוזה הזה מתאים.
type Database struct{ ConnectionString string }
func (d Database) FetchData() string { return "data from DB" }
type APIClient struct{ Endpoint string }
func (a APIClient) FetchData() string { return "data from API" }
func main() {
db := Database{ConnectionString: "localhost"}
api := APIClient{Endpoint: "https://api.example.com"}
ProcessData(db) // עובד!
ProcessData(api) // עובד גם!
}הגישה הזו של חוזה הופכת את הקוד שלך גם לניתן יותר לבדיקה. במהלך הבדיקות, אפשר ליצור טיפוס דמה שעומד בממשק בלי להזדקק למסדי נתונים אמיתיים או לחיבורי רשת. הפונקציה הנבדקת אינה יודעת להבחין בהבדל, כי החוזה מתקיים.
אתגר
קלבואו נבנה מערכת התראות שמדגימה כיצד ממשקים משמשים כחוזים. תיצרו ממשק Notifier שמגדיר מה פירוש שליחת התראה, ואז תממשו כמה ערוצי התראות שמכבדים את החוזה הזה.
תארגנו את הקוד בשני קבצים:
notifiers.go: הגדירו ממשקNotifierעם מתודה אחתNotify(message string) string. לאחר מכן צרו שלוש מבנים שונים שעומדים בחוזה הזה:ConsoleNotifierעם שדהPrefixFileNotifierעם שדהFilenameWebhookNotifierעם שדהURL
Notifyולהחזיר מחרוזת שמתארת כיצד ההתראה תישלח.main.go: צרו פונקציה בשםBroadcastAlertשמקבלת מערך של ערכיNotifierומחרוזת הודעה. הפונקציה צריכה לקרוא ל-Notifyבכל אחד מהמתריעים ולהדפיס את התוצאות. קראו את ההגדרות מהקלט, צרו את שלושת סוגי המתריעים ושדרו התראה דרך כולם.
הקלטים הבאים יסופקו:
- שורה 1: קידומת למסוף
- שורה 2: שם הקובץ
- שורה 3: כתובת URL של webhook
- שורה 4: הודעת התראה
מתודות ה-Notify שלכם צריכות להחזיר מחרוזות בפורמטים הבאים:
- ConsoleNotifier:
[Prefix] [message] - FileNotifier:
Writing to [Filename]: [message] - WebhookNotifier:
POST [URL]: [message]
לדוגמה, עבור ALERT:, log.txt, https://hooks.example.com ו-Server down!, הפלט שלכם צריך להיות:
ALERT: Server down!
Writing to log.txt: Server down!
POST https://hooks.example.com: Server down!היתרון של התכנון הזה הוא שלפונקציה BroadcastAlert שלכם לא ידוע מהם סוגי המתריעים הספציפיים, וזה גם לא משנה לה. היא דורשת רק שכל אחד מהם ימלא את חוזה Notifier. בהמשך תוכלו להוסיף SlackNotifier או EmailNotifier בלי לשנות כלל את פונקציית השידור.
נסו בעצמכם
package main
import (
"bufio"
"fmt"
"os"
)
// BroadcastAlert שולחת הודעה דרך כל אובייקטי ההתראה
// TODO: יש לממש את הפונקציה כך שתבצע את הפעולות הבאות:
// - תקבל פרוסה של ערכי Notifier ומחרוזת הודעה
// - תקרא ל־Notify בכל אובייקט התראה ותדפיס את התוצאות
func BroadcastAlert(notifiers []Notifier, message string) {
// TODO: יש לעבור על אובייקטי ההתראה ולהדפיס את התוצאה של כל התראה
}
func main() {
scanner := bufio.NewScanner(os.Stdin)
// קרא את הקידומת למסוף
scanner.Scan()
prefix := scanner.Text()
// קרא את שם הקובץ
scanner.Scan()
filename := scanner.Text()
// קרא את כתובת ה־URL של ה־webhook
scanner.Scan()
url := scanner.Text()
// קרא את הודעת ההתראה
scanner.Scan()
message := scanner.Text()
// TODO: יש ליצור מופעים של כל שלושת סוגי אובייקטי ההתראה
// - ConsoleNotifier עם הקידומת
// - FileNotifier עם שם הקובץ
// - WebhookNotifier עם ה-URL
// TODO: צרו slice של Notifier המכיל את כל שלושת מנגנוני ההתראה
// TODO: קראו ל-BroadcastAlert עם מנגנוני ההתראה וההודעה
_ = prefix
_ = filename
_ = url
_ = message
fmt.Println("TODO: Implement the solution")
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
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 אונליין