Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

حل بديل للـ Methods العامة

جزء من قسم Object Oriented Programming في رحلة GO على Coddy — الدرس 71 من 107.

تمتلك Go محدودية ملحوظة: لا يمكنك تعريف methods بمعاملات نوع (type parameters) خاصة بها على struct. بينما تعمل الـ generic structs بشكل ممتاز، فإن إضافة method تقدم معامل نوع جديد غير مسموح به.

هذا الكود لن يتم تجميعه:

type Box[T any] struct {
    Value T
}

// خطأ: يجب ألا تحتوي الدالة (method) على معاملات نوع (type parameters)
func (b Box[T]) Convert[U any](fn func(T) U) U {
    return fn(b.Value)
}

الحل البديل هو استخدام دالة عامة (generic function) مستقلة بدلاً من استخدام تابع (method):

type Box[T any] struct {
    Value T
}

// تابع عادي - يستخدم معامل النوع الخاص بالهيكل (struct)
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)  // Number: 42
}

لا يزال بإمكان الأساليب (Methods) في الهياكل العامة (generic structs) استخدام معامل النوع T الخاص بالهيكل. ينطبق القيد فقط على تقديم معاملات نوع إضافية في توقيع الأسلوب.

عندما تحتاج إلى تلك المرونة، فإن الدالة العامة التي تأخذ الـ struct كمعامل أول لها تحقق نفس النتيجة.

challenge icon

التحدي

سهل

لنقم ببناء مجموعة أدوات لتحويل البيانات توضح كيفية الالتفاف على قيود Go المتعلقة بالأساليب العامة (generic methods)! نظرًا لأن الأساليب لا يمكنها تقديم معاملات نوع (type parameters) جديدة، فستقوم بإنشاء دوال عامة مستقلة تحقق نفس المرونة.

ستقوم بتنظيم الكود الخاص بك عبر ملفين:

  • wrapper.go: حدد الحاوية العامة ودوال التحويل الخاصة بك.

    أنشئ هيكلًا عامًا (generic struct) باسم Wrapper[T any] يحتوي على حقل واحد Value من النوع T.

    أضف أسلوبًا (method) باسم 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 لمضاعفة القيمة (بإرجاع 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 لنوع الإرجاع — وهو أمر لن يكون ممكنًا كأسلوب (method) على الهيكل. يمنحك هذا النمط مرونة التحويلات العامة مع العمل ضمن قيود نظام الأنواع في Go.

ورقة مرجعية

لا تسمح لغة Go للأساليب (methods) في الهياكل العامة (generic structs) بتقديم معلمات النوع الخاصة بها. يمكن للأساليب فقط استخدام معلمات النوع الموجودة بالفعل في الهيكل.

هذا الكود لن يتم تجميعه (compile):

type Box[T any] struct {
    Value T
}

// خطأ: يجب ألا يحتوي الأسلوب على معلمات نوع
func (b Box[T]) Convert[U any](fn func(T) U) U {
    return fn(b.Value)
}

الحل البديل هو استخدام دوال عامة (generic functions) مستقلة بدلاً من الأساليب:

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)  // Number: 42
}

يمكن للأساليب في الهياكل العامة استخدام معلمة النوع الخاصة بالهيكل. ينطبق القيد فقط على تقديم معلمات نوع إضافية في توقيع الأسلوب (method signature). عندما تحتاج إلى تلك المرونة، استخدم دالة عامة تأخذ الهيكل كمعامل أول لها.

جرّب بنفسك

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" {
		// تحليل القيمة الصحيحة integer
		num, _ := strconv.Atoi(value)
		
		// TODO: قم بإنشاء Wrapper[int] بالرقم الذي تم تحليله
		
		// TODO: استخدم Transform لمضاعفة القيمة (تلميح: مرر دالة تقوم بالمضاعفة)
		
		// TODO: استخدم TransformToString للحصول على تمثيل نصي
		
		// TODO: اطبع النتائج بالتنسيق المطلوب:
		// Original: [value]
		// Transformed: [transformed value]
		// As String: [string representation]
		
		_ = num // قم بإزالة هذا السطر عند استخدام num
	} else if typeIndicator == "string" {
		// TODO: قم بإنشاء Wrapper[string] بالقيمة
		
		// TODO: استخدم Transform للحصول على طول السلسلة النصية (تلميح: مرر دالة تعيد len())
		
		// TODO: استخدم TransformToString للحصول على تمثيل نصي
		
		// TODO: اطبع النتائج بالتنسيق المطلوب:
		// Original: [value]
		// Transformed: [transformed value]
		// As String: [string representation]
		
		_ = value // قم بإزالة هذا السطر عند استخدام value
	}
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming