מבנים גנריים
חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 70 מתוך 107.
בדומה לפונקציות, גם למבנים (structs) יכולים להיות פרמטרים של טיפוס. מבנה גנרי מאפשר לך ליצור מבני נתונים שעובדים עם כל טיפוס תוך שמירה על בטיחות טיפוסים. זה מושלם לבניית מבני אחסון לשימוש חוזר, כמו מחסניות, תורים או זוגות מפתח-ערך.
כדי להגדיר מבנה גנרי, הציבו את פרמטר הטיפוס אחרי שם המבנה:
type Box[T any] struct {
Value T
}
func main() {
intBox := Box[int]{Value: 42}
strBox := Box[string]{Value: "hello"}
fmt.Println(intBox.Value) // 42
fmt.Println(strBox.Value) // שלום
}בניגוד לפונקציות גנריות, כשאתה יוצר מופע של מבנה גנרי, עליך לציין במפורש את הטיפוס. Go אינה יכולה להסיק את הטיפוס מההקשר בלבד.
למבנים גנריים יכולים להיות כמה פרמטרים של טיפוס, ולכן הם אידיאליים למבנים כמו זוגות או מפות:
type Pair[K, V any] struct {
Key K
Value V
}
p := Pair[string, int]{Key: "age", Value: 25}
fmt.Printf("%s: %d\n", p.Key, p.Value) // age: 25אפשר גם להחיל אילוצים כדי לוודא שהטיפוסים המאוחסנים תומכים בפעולות מסוימות:
type NumberBox[T int | float64] struct {
Value T
}
nb := NumberBox[float64]{Value: 3.14}מבנים גנריים הם הבסיס לבניית מבני נתונים לשימוש חוזר ובטיחותיים מבחינת טיפוסים ב-Go. בשיעור הבא תלמדו כיצד להגדיר מתודות על הטיפוסים הגנריים האלה.
אתגר
קלבואו נבנה מערכת מלאי גנרית שיכולה לעקוב אחר סוגים שונים של פריטים! תיצרו מבני מכלים לשימוש חוזר שעובדים עם כל סוג, תוך שמירה על בטיחות טיפוסים מלאה.
תארגנו את הקוד שלכם בשני קבצים:
inventory.go: הגדירו את מבני המכלים הגנריים שלכם.צרו מבנה גנרי
Item[T any]עם שני שדות:Name(מחרוזת) ו-Data(מטיפוס T). הוא מייצג כל פריט עם נתונים משויכים מטיפוס גמיש.צרו מבנה גנרי
Container[T any]עם שדה יחידItemsשמכיל פרוסה שלItem[T]. המכל הזה יכול לאחסן כמה פריטים מאותו טיפוס נתונים.צרו מבנה גנרי מוגבל
PricedItem[T int | float64]עם שלושה שדות:Name(מחרוזת),Quantity(מספר שלם) ו-Price(מטיפוס T). האילוץ מבטיח שהמחירים יהיו תמיד מספריים.ממשו פונקציה
NewContainer[T any]() *Container[T]שיוצרת ומחזירה מצביע ל-Container ריק.ממשו מתודה
Addעבור*Container[T]שמקבלת שם (מחרוזת) ונתונים (T), יוצרת Item ומוסיפה אותו לפרוסת Items של המכל.ממשו מתודה
CountעבורContainer[T]שמחזירה את מספר הפריטים במכל.main.go: הדגימו את המבנים הגנריים שלכם עם טיפוסים שונים.קראו טיפוס פריט (
string,intאוpriced), ואז קראו מספר פריטים ולאחריו את פרטי הפריטים.עבור הטיפוס
string: קראו זוגות של שם ונתונים מסוג מחרוזת. צרוContainer[string], הוסיפו את כל הפריטים, ואז הדפיסו כל פריט בתבנית[Name]: [Data]ואחריו את המספר הכולל.עבור הטיפוס
int: קראו זוגות של שם ונתונים מסוג מספר שלם. צרוContainer[int], הוסיפו את כל הפריטים, ואז הדפיסו כל פריט בתבנית[Name]: [Data]ואחריו את המספר הכולל.עבור הטיפוס
priced: קראו שלשות של שם, כמות ומחיר (כמספר עשרוני). צרו ישירות מופעים שלPricedItem[float64]והדפיסו כל אחד בתבנית[Name] x[Quantity] @ [Price], כשהמחיר מוצג עם ספרה אחת אחרי הנקודה העשרונית.הדפיסו את שורת הספירה כך:
Total items: [count]
הקלטים הבאים יסופקו:
- שורה 1: טיפוס הפריט (
string,intאוpriced) - שורה 2: מספר הפריטים (מספר שלם)
- השורות הבאות: פרטי הפריטים בהתאם לטיפוס
לדוגמה, בהינתן:
string
3
Book
Fiction Novel
Pen
Blue Ink
Notebook
Lined Paperהפלט שלכם צריך להיות:
Book: Fiction Novel
Pen: Blue Ink
Notebook: Lined Paper
Total items: 3ובהינתן:
int
2
Apples
50
Oranges
30הפלט שלכם צריך להיות:
Apples: 50
Oranges: 30
Total items: 2ובהינתן:
priced
2
Widget
10
19.99
Gadget
5
49.50הפלט שלכם צריך להיות:
Widget x10 @ 19.9
Gadget x5 @ 49.5
Total items: 2שימו לב שאותו מבנה Container עובד בצורה חלקה גם עם מחרוזות וגם עם מספרים שלמים, ואילו PricedItem משתמש באילוץ כדי להבטיח שרק טיפוסים מספריים יוכלו לשמש למחירים. עליכם לציין במפורש את פרמטר הטיפוס כשיוצרים מופעים של המבנים הגנריים האלה.
נסו בעצמכם
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// קרא את סוג הפריט
scanner.Scan()
itemType := scanner.Text()
// קרא את count
scanner.Scan()
count, _ := strconv.Atoi(scanner.Text())
switch itemType {
case "string":
// TODO: צור Container[string] באמצעות NewContainer
// TODO: קרא 'count' פריטים (לכל אחד שם בשורה אחת ונתונים בשורה הבאה)
// TODO: הוסף כל פריט למכל
// TODO: הדפס כל פריט בפורמט "[Name]: [Data]"
// TODO: הדפס "Total items: [count]" באמצעות המתודה Count
case "int":
// TODO: צור Container[int] באמצעות NewContainer
// TODO: קרא 'count' פריטים (לכל אחד שם בשורה אחת ונתון מספרי שלם בשורה הבאה)
// TODO: הוסף כל פריט למכל
// TODO: הדפס כל פריט בפורמט "[Name]: [Data]"
// TODO: הדפס "Total items: [count]" באמצעות המתודה Count
case "priced":
// TODO: צור slice להחזקת מופעים של PricedItem[float64]
// TODO: קרא 'count' פריטים (לכל אחד שם, כמות ומחיר בשורות נפרדות)
// TODO: צור PricedItem[float64] לכל פריט והוסף אותו ל-slice
// TODO: הדפס כל פריט בפורמט "[Name] x[Quantity] @ [Price]", כשהמחיר מוצג עם ספרה אחת אחרי הנקודה העשרונית
// TODO: הדפס "Total items: [count]"
}
_ = scanner // השתמש ב-scanner לקריאת הקלט
_ = count // השתמש ב-count עבור הלולאה
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
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 אונליין