Menu
Coddy logo textTech

פתרון עוקף למתודות גנריות

חלק מהיחידה תכנות מונחה עצמים במסלול ה-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 של המבנה. ההגבלה חלה רק על הוספת פרמטרי טיפוס נוספים בחתימת המתודה.

כשאתה זקוק לגמישות הזאת, פונקציה גנרית שמקבלת את המבנה כארגומנט הראשון שלה משיגה את אותה התוצאה.

challenge icon

אתגר

קל

בואו נבנה ערכת כלים להתמרת נתונים שמדגימה את הדרך לעקוף את המגבלה של 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
	}
}
quiz iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר Go אונליין