פתרון עוקף למתודות גנריות
חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 71 מתוך 107.
ל-Go יש מגבלה בולטת: אי אפשר להגדיר על מבנה שיטות עם פרמטרים טיפוסיים משלהן. אף שמבנים גנריים עובדים מצוין, אסור להוסיף שיטה שמציגה פרמטר טיפוסי חדש.
הקוד הזה לא יתקמפל:
type Box[T any] struct {
Value T
}
// ERROR: למתודה אסור שיהיו פרמטרים של טיפוס
func (b Box[T]) Convert[U any](fn func(T) U) U {
return fn(b.Value)
}הפתרון העוקף הוא להשתמש בפונקציה גנרית עצמאית במקום בשיטה:
type Box[T any] struct {
Value T
}
// מתודה רגילה - משתמשת בפרמטר הטיפוס של המבנה
func (b Box[T]) Get() T {
return b.Value
}
// פונקציה עצמאית - יכולה להגדיר פרמטרי טיפוס משלה
func Convert[T, U any](b Box[T], fn func(T) U) U {
return fn(b.Value)
}
func main() {
intBox := Box[int]{Value: 42}
// השתמשו במתודה
fmt.Println(intBox.Get()) // 42
// השתמשו בפונקציה העצמאית
strResult := Convert(intBox, func(n int) string {
return fmt.Sprintf("Number: %d", n)
})
fmt.Println(strResult) // מספר: 42
}מתודות במבנים גנריים עדיין יכולות להשתמש בפרמטר הטיפוס T של המבנה. ההגבלה חלה רק על הוספת פרמטרי טיפוס נוספים בחתימת המתודה.
כשאתה זקוק לגמישות הזאת, פונקציה גנרית שמקבלת את המבנה כארגומנט הראשון שלה משיגה את אותה התוצאה.
אתגר
קלבואו נבנה ערכת כלים להתמרת נתונים שמדגימה את הדרך לעקוף את המגבלה של Go על מתודות גנריות! מכיוון שמתודות לא יכולות להגדיר פרמטרי טיפוס חדשים, תיצרו פונקציות גנריות עצמאיות שמספקות את אותה גמישות.
תארגנו את הקוד בשני קבצים:
wrapper.go: הגדירו את המכולה הגנרית ואת פונקציות ההתמרה.צרו מבנה גנרי
Wrapper[T any]עם שדה יחידValueמטיפוסT.הוסיפו מתודה
Getל-Wrapper[T]שמחזירה את הערך העטוף. המתודה משתמשת בפרמטר הטיפוס של המבנה, וזה מותר.צרו פונקציה גנרית עצמאית
Transform[T, U any](w Wrapper[T], fn func(T) U) Uשמחילה את פונקציית ההתמרה על הערך שבמכולה ומחזירה את התוצאה. הפונקציה הזאת זקוקה לפרמטר טיפוס משלהUעבור טיפוס הפלט, ולכן היא חייבת להיות פונקציה עצמאית ולא מתודה.צרו פונקציה עצמאית נוספת
TransformToString[T any](w Wrapper[T]) stringשממירה את הערך העטוף למחרוזת באמצעותfmt.Sprintf("%v", ...).main.go: הדגימו את דפוס העקיפה באמצעות התמרות שונות.קראו מציין טיפוס (
intאוstring), ואז קראו ערך. צרוWrapperמהטיפוס המתאים.עבור קלט מסוג
int: צרוWrapper[int], ואז השתמשו ב-Transformכדי להכפיל את הערך ב-2 (ולהחזיר int), והשתמשו ב-TransformToStringכדי לקבל ייצוג של מחרוזת.עבור קלט מסוג
string: צרוWrapper[string], ואז השתמשו ב-Transformכדי לקבל את אורך המחרוזת (ולהחזיר int), והשתמשו ב-TransformToStringכדי לקבל ייצוג של המחרוזת.הדפיסו את התוצאות בפורמט הזה:
Original: [value] Transformed: [transformed value] As String: [string representation]
הקלטים הבאים יסופקו:
- שורה 1: מציין טיפוס (
intאוstring) - שורה 2: הערך
לדוגמה, עבור הקלט:
int
25הפלט שלכם צריך להיות:
Original: 25
Transformed: 50
As String: 25ועבור הקלט:
string
Hello Worldהפלט שלכם צריך להיות:
Original: Hello World
Transformed: 11
As String: Hello Worldהתובנה המרכזית היא ש-Transform מקבלת Wrapper[T] כארגומנט הראשון שלה ומגדירה פרמטר טיפוס חדש U עבור טיפוס הערך המוחזר. דבר שלא היה אפשרי אילו הייתה מתודה של המבנה. הדפוס הזה מעניק לכם את הגמישות של התמרות גנריות, תוך עבודה במסגרת המגבלות של מערכת הטיפוסים של Go.
נסו בעצמכם
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// קרא את מציין הסוג
typeIndicator, _ := reader.ReadString('\n')
typeIndicator = strings.TrimSpace(typeIndicator)
// קרא את הערך
value, _ := reader.ReadString('\n')
value = strings.TrimSpace(value)
if typeIndicator == "int" {
// המר את הערך למספר שלם
num, _ := strconv.Atoi(value)
// TODO: צור Wrapper[int] עם המספר שהומר
// TODO: השתמש ב-Transform כדי להכפיל את הערך פי שניים (רמז: העבר פונקציה שמכפילה פי שניים)
// TODO: השתמש ב-TransformToString כדי לקבל ייצוג כמחרוזת
// TODO: הדפס את התוצאות בפורמט הנדרש:
// Original: [value]
// Transformed: [הערך לאחר ההמרה]
// As String: [ייצוג כמחרוזת]
_ = num // הסר את השורה הזו כשתשתמש ב-num
} else if typeIndicator == "string" {
// TODO: צור Wrapper[string] עם הערך
// TODO: השתמשו ב-Transform כדי לקבל את אורך המחרוזת (רמז: העבירו פונקציה שמחזירה len())
// TODO: השתמש ב-TransformToString כדי לקבל ייצוג כמחרוזת
// TODO: הדפס את התוצאות בפורמט הנדרש:
// Original: [value]
// Transformed: [הערך לאחר ההמרה]
// As String: [ייצוג כמחרוזת]
_ = value // הסירו את השורה הזו כשתשתמשו ב-value
}
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
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 אונליין