פולימורפיזם באמצעות ממשקים
חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 41 מתוך 107.
פולימורפיזם מאפשר להתייחס לסוגים שונים באופן אחיד באמצעות ממשק משותף. ב-Go, הדבר מושג כולו באמצעות ממשקים, ללא הורשה או היררכיות מחלקות.
כשפונקציה מקבלת טיפוס ממשק כפרמטר, אפשר להעביר לה כל טיפוס קונקרטי שמממש את הממשק הזה. הפונקציה לא צריכה לדעת מהו הטיפוס הספציפי. היא מתייחסת רק להתנהגות שהוגדרה על ידי הממשק:
type Speaker interface {
Speak() string
}
type Dog struct{ Name string }
func (d Dog) Speak() string { return "Woof!" }
type Cat struct{ Name string }
func (c Cat) Speak() string { return "Meow!" }
func MakeSound(s Speaker) {
fmt.Println(s.Speak())
}
עכשיו MakeSound פועלת עם כל טיפוס שיש לו מתודה Speak():
func main() {
dog := Dog{Name: "Rex"}
cat := Cat{Name: "Whiskers"}
MakeSound(dog) // הב הב!
MakeSound(cat) // מיאו!
}
אותה קריאה לפונקציה מפיקה התנהגות שונה בהתאם לסוג בפועל שמועבר. זוהי פולימורפיות בפעולה. הפונקציה MakeSound נכתבת פעם אחת, אך עובדת עם מספר בלתי מוגבל של סוגים, כל עוד הם עומדים בממשק Speaker.
הגישה הזו שומרת על הקוד שלך גמיש וניתן להרחבה. כדי להוסיף סוג חדש שמפיק צלילים, אין צורך לשנות פונקציות קיימות; פשוט מממשים את הממשק, וזה עובד אוטומטית.
אתגר
קלבואו נבנה מערכת לתיאור כלי רכב שמדגימה פולימורפיזם בפעולה. תיצרו סוגים שונים של כלי רכב, שלכולם יש התנהגות משותפת באמצעות ממשק, ואז תכתבו פונקציה אחת שעובדת עם כל כלי רכב.
תארגנו את הקוד שלכם בשני קבצים:
vehicles.go: הגדירו ממשקDescriberשדורש מתודהDescribe() string. לאחר מכן צרו שלושה סוגי כלי רכב, שכל אחד מהם מממש את הממשק בדרכו:Carעם שדותBrandו-Model. המתודהDescribe()שלו מחזירהCar: [Brand] [Model]Motorcycleעם השדותBrandו-EngineCC(int). המתודהDescribe()שלו מחזירהMotorcycle: [Brand] [EngineCC]ccBicycleעם השדהType(כמו "Mountain" או "Road"). המתודהDescribe()שלו מחזירהBicycle: [Type]
main.go: צרו פונקציה בשםPrintDescriptionשמקבלת כל ערך שמממש אתDescriberומדפיסה את התוצאה של קריאה ל-Describe(). קראו את פרטי כלי הרכב מהקלט, צרו כלי רכב אחד מכל סוג, והעבירו כל אחד מהם אלPrintDescriptionכדי להדגים שאותה פונקציה עובדת עם שלושת הסוגים השונים.
הקלטים הבאים יסופקו:
- שורה 1: יצרן המכונית
- שורה 2: דגם המכונית
- שורה 3: יצרן האופנוע
- שורה 4: נפח המנוע של האופנוע בסמ"ק (מספר שלם)
- שורה 5: סוג האופניים
לדוגמה, בהינתן Toyota, Camry, Honda, 600 ו-Mountain, הפלט שלכם צריך להיות:
Car: Toyota Camry
Motorcycle: Honda 600cc
Bicycle: Mountainשימו לב ש-PrintDescription לא צריכה לדעת אם היא מקבלת Car, Motorcycle או Bicycle. היא פשוט קוראת ל-Describe(), וכל סוג מחזיר פלט ייחודי משלו. זהו פולימורפיזם: פונקציה אחת, התנהגויות מרובות.
נסו בעצמכם
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
// TODO: צרו פונקציה בשם PrintDescription שמקבלת כל Describer
// ומדפיסה את התוצאה של קריאה ל-Describe()
func main() {
reader := bufio.NewReader(os.Stdin)
// קראו את פרטי המכונית
carBrand, _ := reader.ReadString('\n')
carBrand = strings.TrimSpace(carBrand)
carModel, _ := reader.ReadString('\n')
carModel = strings.TrimSpace(carModel)
// קראו את פרטי האופנוע
motoBrand, _ := reader.ReadString('\n')
motoBrand = strings.TrimSpace(motoBrand)
motoEngineStr, _ := reader.ReadString('\n')
motoEngineStr = strings.TrimSpace(motoEngineStr)
motoEngine, _ := strconv.Atoi(motoEngineStr)
// קראו את פרטי האופניים
bicycleType, _ := reader.ReadString('\n')
bicycleType = strings.TrimSpace(bicycleType)
// TODO: צרו Car, Motorcycle ו-Bicycle באמצעות ערכי הקלט
// TODO: קראו ל-PrintDescription עבור כל כלי רכב כדי להדגים פולימורפיזם
fmt.Println("TODO: Print vehicle descriptions")
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
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 אונליין