מימוש משתמע
חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 26 מתוך 107.
בשפות מונחות עצמים רבות כמו Java או C#, צריך להצהיר במפורש שסוג מממש ממשק באמצעות מילות מפתח כמו implements. Go נוקטת גישה אחרת: מימוש ממשק הוא מרומז.
טיפוס עומד בדרישות של ממשק פשוט על ידי מימוש כל המתודות שלו. אין צורך להצהיר על הקשר בשום מקום. המהדר מסיק זאת באופן אוטומטי.
type Writer interface {
Write(data string) int
}
type FileWriter struct {
Filename string
}
// FileWriter מממש את Writer באופן מרומז
func (f FileWriter) Write(data string) int {
fmt.Println("Writing to", f.Filename)
return len(data)
}
שימו לב שאין הצהרת implements Writer ב-FileWriter. מכיוון של-FileWriter יש מתודה Write(data string) int שתואמת לחתימת הממשק, הוא מקיים באופן אוטומטי את הממשק Writer.
לעיצוב הזה יש תוצאה עוצמתית: אפשר להגדיר ממשקים אחרי שסוגים כבר קיימים. אם לספרייה של צד שלישי יש סוג עם מתודות שנחוצות לך, אפשר ליצור ממשק שהסוג הזה כבר עומד בדרישות שלו, בלי לשנות את הקוד המקורי.
func Save(w Writer, content string) {
w.Write(content)
}
func main() {
fw := FileWriter{Filename: "data.txt"}
Save(fw, "Hello!") // עובד כי FileWriter מממש את Writer
}
מימוש משתמע שומר על קוד Go מנותק וגמיש. טיפוסים לא צריכים להכיר ממשקים מראש, וכך קל יותר להרכיב מערכות מחלקים עצמאיים.
אתגר
קלבואו נבנה מערכת להעברת הודעות שמדגימה כיצד טיפוסים מממשים ממשקים באופן מרומז ב-Go. תיצרו טיפוסי שליחים שונים שכולם תואמים לאותו ממשק, בלי להצהיר במפורש על הקשר הזה.
תארגנו את הקוד שלכם בשני קבצים:
messengers.go: הגדירו ממשקMessengerעם מתודה יחידהSend(message string) string. לאחר מכן צרו שתי מבנים שיתאימו לממשק הזה באופן מרומז:EmailMessengerעם שדהAddress(מחרוזת)SMSMessengerעם שדהPhoneNumber(מחרוזת)
Sendשמחזירה מחרוזת המתארת את המסירה. אף אחד מהמבנים לא צריך להצהיר במפורש שהוא מממש אתMessenger: Go יזהה זאת אוטומטית על סמך חתימות המתודות.main.go: צרו פונקציה בשםDeliverMessageשמקבלת כלMessengerומחרוזת הודעה, ולאחר מכן מחזירה את התוצאה של הקריאה ל-Send. קראו את פרטי הקשר מהקלט, צרו את שני טיפוסי השליחים והשתמשו בפונקציה שלכם כדי להעביר הודעות באמצעות כל אחד מהם.
הקלט הבא יסופק:
- שורה 1: כתובת אימייל
- שורה 2: מספר טלפון
- שורה 3: הודעה לשליחה
המתודות Send שלכם צריכות להחזיר מחרוזות בפורמטים הבאים:
- EmailMessenger:
Email to [Address]: [message] - SMSMessenger:
SMS to [PhoneNumber]: [message]
לדוגמה, עבור alice@example.com, 555-1234 ו-Hello!, הפלט שלכם צריך להיות:
Email to alice@example.com: Hello!
SMS to 555-1234: Hello!התובנה המרכזית כאן היא שהפונקציה DeliverMessage שלכם מקבלת כל Messenger, וגם EmailMessenger וגם SMSMessenger תואמים לממשק הזה פשוט משום שיש להם מתודת Send תואמת. אין כאן שום מילת מפתח implements: המימוש המרומז של Go מטפל בכל זה.
נסו בעצמכם
package main
import (
"bufio"
"fmt"
"os"
)
// TODO: צרו פונקציה בשם DeliverMessage שמקבלת כל Messenger
// ומחרוזת הודעה, ואז מחזירה את תוצאת הקריאה ל-Send
func main() {
reader := bufio.NewReader(os.Stdin)
// קראו את כתובת האימייל
email, _ := reader.ReadString('\n')
email = email[:len(email)-1]
// קראו את מספר הטלפון
phone, _ := reader.ReadString('\n')
phone = phone[:len(phone)-1]
// קראו את ההודעה
message, _ := reader.ReadString('\n')
if len(message) > 0 && message[len(message)-1] == '\n' {
message = message[:len(message)-1]
}
// TODO: צרו EmailMessenger עם כתובת האימייל
// TODO: צרו SMSMessenger עם מספר הטלפון
// TODO: השתמשו ב-DeliverMessage כדי לשלוח את ההודעה דרך כל Messenger
// והדפיסו את התוצאות
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
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 אונליין