טיפוס ברווז ב-Go
חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 42 מתוך 107.
המונח טיפוס ברווז מקורו באמרה: "אם הוא הולך כמו ברווז ומגעגע כמו ברווז, אז הוא ברווז." ב-Go, פירוש הדבר שטיפוס לא צריך להצהיר במפורש שהוא מממש ממשק. הוא רק צריך לכלול את המתודות המתאימות.
נבחן את הממשק הזה ואת שני הטיפוסים שאינם קשורים זה לזה כלל:
type Quacker interface {
Quack() string
}
type Duck struct{}
func (d Duck) Quack() string { return "Quack!" }
type Robot struct{}
func (r Robot) Quack() string { return "Beep-quack!" }
לא Duck ולא Robot מזכירים את Quacker בשום מקום בהגדרות שלהם. ובכל זאת, שניהם מממשים את הממשק פשוט משום שיש להם מתודת Quack() עם החתימה הנכונה:
func MakeItQuack(q Quacker) {
fmt.Println(q.Quack())
}
func main() {
MakeItQuack(Duck{}) // קוואק!
MakeItQuack(Robot{}) // ביפ-קוואק!
}
ההתאמה המרומזת הזו עוצמתית, כי היא מאפשרת לך להגדיר ממשקים לאחר שהטיפוסים כבר קיימים. אפשר ליצור ממשק שתואם לטיפוסים מחבילות חיצוניות בלי לשנות את קוד המקור שלהן. הטיפוסים לא צריכים לדעת על הממשק שלך. הם רק צריכים להתנהג כראוי.
טיפוס ברווזי ב-Go מעניק לך את הגמישות של שפות דינמיות, תוך שמירה על בטיחות טיפוסים בזמן הקומפילציה. המהדר מוודא שלטיפוסים יש בפועל את המתודות הנדרשות לפני שהוא מאפשר להשתמש בהם כערכי ממשק.
אתגר
קלבואו נבנה מערכת להעברת הודעות שמדגימה טיפוסיות ברווזית בפעולה. תיצרו טיפוסים שאינם קשורים זה לזה, שכולם יכולים לשלוח הודעות דרך ממשק משותף, בלי שאף אחד מהם יצהיר במפורש שהוא מממש אותו.
תארגנו את הקוד שלכם בשלושה קבצים:
messengers.go: צרו שלוש מבנים שאינם קשורים זה לזה, אך במקרה חולקים אותה התנהגות:Phoneעם שדהNumber. המתודהSendMessage(text string) stringשלו מחזירהSMS to [Number]: [text]Computerעם שדהEmail. המתודהSendMessage(text string) stringשלו מחזירהEmail to [Email]: [text]Pigeonעם שדהName. המתודהSendMessage(text string) stringשלו מחזירה[Name] carries: [text]
sender.go: הגדירו ממשקMessengerשדורש מתודהSendMessage(text string) string. צרו פונקציה בשםBroadcastשמקבלת פרוסה שלMessengerומחרוזת הודעה, ואז מחזירה פרוסת מחרוזות שמכילה את התוצאה של קריאה ל-SendMessageבכל שליח.main.go: קראו את הפרטים מהקלט, צרו אחד מכל סוג של שליח, אספו אותם לפרוסה שלMessenger, והשתמשו ב-Broadcastכדי לשלוח הודעה דרך כולם. הדפיסו כל תוצאה בשורה נפרדת.
הקלטים הבאים יסופקו:
- שורה 1: מספר טלפון
- שורה 2: כתובת דוא"ל
- שורה 3: שם היונה
- שורה 4: הודעה להפצה
לדוגמה, בהינתן 555-1234, alice@mail.com, Winston ו-Hello World, הפלט שלכם צריך להיות:
SMS to 555-1234: Hello World
Email to alice@mail.com: Hello World
Winston carries: Hello Worldהתובנה המרכזית כאן היא של-Phone, ל-Computer ול-Pigeon אין שום דבר משותף. הם לא חולקים טיפוס בסיס ואינם מממשים במפורש את Messenger. ובכל זאת, מכיוון שכולם "געגעים" באותו אופן (יש להם מתודה תואמת), Go מאפשרת להשתמש בכולם כערכי Messenger. זו טיפוסיות ברווזית בפעולה.
נסו בעצמכם
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// קרא מספר טלפון
scanner.Scan()
phoneNumber := scanner.Text()
// קרא כתובת דוא״ל
scanner.Scan()
email := scanner.Text()
// קרא את שם היונה
scanner.Scan()
pigeonName := scanner.Text()
// קרא את ההודעה לשידור
scanner.Scan()
message := scanner.Text()
// TODO: צור מופע אחד מכל סוג של שליח (Phone, Computer, Pigeon)
// TODO: אסוף את כל השליחים ל-slice של Messenger
// TODO: השתמש ב-Broadcast כדי לשלוח את ההודעה דרך כל השליחים
// TODO: הדפס כל תוצאה בשורה משלה
// מציין מקום לשימוש במשתנים (יש להסיר בעת המימוש)
_ = phoneNumber
_ = email
_ = pigeonName
_ = message
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
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 אונליין