حل بديل للـ 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 كمعامل أول لها تحقق نفس النتيجة.
التحدي
سهللنقم ببناء مجموعة أدوات لتحويل البيانات توضح كيفية الالتفاف على قيود 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
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP في Go
الملفات الخارجيةمساحة العمل والـ Modules في Goالـ Packages والـ Importsالأسماء المصدرة مقابل غير المصدرةمقدمة إلى الـ OOP في Goالـ Structs كـ Classesتعريف الـ Methods في الـ Structsالـ Pointer Receivers مقابل الـ Value Receiversتهيئة الـ Structدوال الـ Constructorمراجعة - آلة حاسبة بسيطة4الواجهات
مقدمة في الواجهاتالتنفيذ الضمنيالواجهة كعقدالواجهة الفارغة (any)تأكيد النوع (Type Assertion)تبديل النوع (Type Switch)تركيب الواجهاتواجهات Stringer و Errorمراجعة - حاسبة الأشكال7التغليف
الحقول المصدرة وغير المصدرةالتغليف على مستوى الحزمةدوال الـ Getter والـ Setterإخفاء المعلومات في Goملخص - سجلات الطلاب10الأنواع العامة (Go 1.18+)
مقدمة في الأنواع العامةمعاملات الأنواعقيود الأنواعالـ Structs العامةحل بديل للـ Methods العامةملخص - المجموعات العامة13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Abstract Factoryنمط Observerنمط Strategy2تعمق في الأنواع و Structs
الأنواع الأساسية والمركبةتعريفات الأنواع المخصصةStruct TagsStructs مجهولةStructs متداخلةالقيم الصفرية والافتراضيةمراجعة - دفتر العناوين5التركيب بدلاً من الوراثة
لماذا لا تدعم Go الوراثةأساسيات تضمين الـ Structترقية الـ Methodتضمين عدة Structsالتضمين مقابل التجميعحجب الـ Methods المضمنةملخص - الهيكل الهرمي للموظفين8معالجة الأخطاء و OOP
واجهة errorأنواع الأخطاء المخصصةتغليف الأخطاء (fmt.Errorf)أخطاء Sentinelerrors.Is() و errors.As()Panic و Defer و Recoverملخص - File Parser11المكتبة القياسية والبرمجة كائنية التوجه (OOP)
io.Reader و io.Writerواجهة sort.Interfaceواجهة fmt.Stringerencoding/json مع Structsواجهة http.Handlerمراجعة - نماذج REST API14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبMiddleware كنمط مزين3المؤشرات والذاكرة
أساسيات المؤشرات في Goالمؤشرات إلى الـ Structsالتمرير بالقيمة مقابل التمرير بالمرجعدالة ()newGarbage Collection في Goملخص - بناء Linked List6تعدد الأشكال في Go
تعدد الأشكال عبر InterfacesDuck Typing في Goقواعد استيفاء الـ Interfaceمجموعات متعددة الأشكالحقن التبعيةمراجعة - معالج الدفع9التزامن والبرمجة كائنية التوجه (OOP)
أساسيات الـ Goroutinesالقنوات (Channels) والاتصالالقنوات المخزنة (Buffered) مقابل غير المخزنةجملة Selectsync.Mutex و sync.RWMutexsync.WaitGroupتصميم الـ Structs الآمنة للخيوط (Thread-Safe)مراجعة - Worker Pool