أساسيات الـ Reflection
جزء من قسم البرمجة كائنية التوجه في رحلة GO على Coddy. الدرس 83 من 107.
الانعكاس يتيح للبرنامج فحص بنيته والتعامل معها أثناء وقت التشغيل. توفّر حزمة Go المسماة reflect هذه الإمكانية، مما يمكّنك من فحص الأنواع، وقراءة fields الخاصة بـ struct، واستدعاء الأساليب ديناميكيًا.
النوعان الأساسيان في reflection هما reflect.Type وreflect.Value. تحصل عليهما باستخدام reflect.TypeOf() وreflect.ValueOf():
import "reflect"
type Person struct {
Name string
Age int
}
p := Person{Name: "Alice", Age: 30}
t := reflect.TypeOf(p)
v := reflect.ValueOf(p)
fmt.Println(t.Name()) // Person
fmt.Println(t.Kind()) // struct
fmt.Println(v.NumField()) // 2يمكنك التكرار عبر حقول البنية لفحص أسمائها وأنواعها وقيمها:
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
value := v.Field(i)
fmt.Printf("%s: %v\n", field.Name, value)
}
// المخرجات:
// Name: Alice
// Age: 30تُعدّ reflection قوية، لكنها تأتي مع مقايضات: فهي تتجاوز التحقّق من الأنواع في وقت الترجمة، وتعمل ببطء أكبر من الوصول المباشر، وتجعل فهم الشيفرة أكثر صعوبة. استخدمها باعتدال في مهام مثل تسلسل JSON، أو تعيين ORM، أو بناء أدوات مساعدة عامة عندما لا يكون النوع معروفًا في وقت الترجمة.
التحدي
سهللنبنِ فاحصًا للبُنى يستخدم الانعكاس لفحص أي بنية والإبلاغ عن بنيتها! هذه حالة استخدام عملية للانعكاس: إنشاء أدوات تعمل مع أنواع غير معروفة وقت الترجمة.
ستنظّم كودك عبر ملفين:
inspector.go: أنشئ أداة الفحص المعتمدة على الانعكاس.أنشئ دالة
InspectStruct(v any) stringتستخدم الحزمةreflectلفحص أي بنية تُمرَّر إليها. يجب أن تُرجع دالتك تقريرًا منسقًا يتضمن:- اسم نوع البنية
- نوع البنية (يجب أن يكون "struct")
- عدد الحقول
- لكل حقل: اسم الحقل ونوعه وقيمته الحالية
يجب أن يكون تنسيق الإخراج كما يلي:
Type: [TypeName] Kind: struct Fields: [count] - [FieldName] ([FieldType]): [Value] - [FieldName] ([FieldType]): [Value] ...استخدم
reflect.TypeOf()للحصول على معلومات النوع، وreflect.ValueOf()للوصول إلى قيم الحقول. كرّر التكرار عبر الحقول باستخدامNumField()وField(i)على النوع للحصول على البيانات الوصفية، وField(i)على القيمة للحصول على القيم الفعلية.main.go: عرّف بُنى نموذجية واستخدم الفاحص.اقرأ نوع بنية (
productأوemployee) وقيم حقولها، ثم أنشئ البنية المناسبة وافحصها.بالنسبة إلى
product: أنشئ بنيةProductتحتوي على حقولName(من النوع string)، وPrice(من النوع float64)، وInStock(من النوع bool). اقرأ الاسم والسعر وحالة التوفر.بالنسبة إلى
employee: أنشئ بنيةEmployeeتحتوي على حقولID(من النوع int)، وName(من النوع string)، وDepartment(من النوع string). اقرأ المعرّف والاسم والقسم.مرّر البنية التي أنشأتها إلى
InspectStructواطبع النتيجة.
سيتم توفير المدخلات التالية:
- السطر 1: نوع البنية (
productأوemployee) - الأسطر التالية: قيم حقول ذلك النوع من البنية
على سبيل المثال، عند إدخال:
product
Laptop
999.99
trueيجب أن يكون إخراجك:
Type: Product
Kind: struct
Fields: 3
- Name (string): Laptop
- Price (float64): 999.99
- InStock (bool): trueوعند إدخال:
employee
42
Alice Johnson
Engineeringيجب أن يكون إخراجك:
Type: Employee
Kind: struct
Fields: 3
- ID (int): 42
- Name (string): Alice Johnson
- Department (string): Engineeringوعند إدخال:
product
Headphones
79.50
falseيجب أن يكون إخراجك:
Type: Product
Kind: struct
Fields: 3
- Name (string): Headphones
- Price (float64): 79.5
- InStock (bool): falseلاحظ كيف يعمل الفاحص مع أنواع مختلفة من البُنى من دون معرفة بنيتها مسبقًا. هذه هي قوة الانعكاس! تتعامل الدالة نفسها InspectStruct مع بُنى Product وEmployee بشكل ديناميكي.
جرّب بنفسك
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
// هيكل Product مع حقول Name و Price و InStock
type Product struct {
Name string
Price float64
InStock bool
}
// هيكل Employee مع حقول ID و Name و Department
type Employee struct {
ID int
Name string
Department string
}
func main() {
reader := bufio.NewReader(os.Stdin)
// اقرأ نوع الهيكل
structType, _ := reader.ReadString('\n')
structType = strings.TrimSpace(structType)
if structType == "product" {
// اقرأ حقول المنتج
name, _ := reader.ReadString('\n')
name = strings.TrimSpace(name)
priceStr, _ := reader.ReadString('\n')
priceStr = strings.TrimSpace(priceStr)
price, _ := strconv.ParseFloat(priceStr, 64)
inStockStr, _ := reader.ReadString('\n')
inStockStr = strings.TrimSpace(inStockStr)
inStock, _ := strconv.ParseBool(inStockStr)
// TODO: أنشئ هيكل Product بالقيم المقروءة
// TODO: استدعِ InspectStruct مع المنتج واطبع النتيجة
} else if structType == "employee" {
// اقرأ حقول الموظف
idStr, _ := reader.ReadString('\n')
idStr = strings.TrimSpace(idStr)
id, _ := strconv.Atoi(idStr)
name, _ := reader.ReadString('\n')
name = strings.TrimSpace(name)
department, _ := reader.ReadString('\n')
department = strings.TrimSpace(department)
// TODO: أنشئ هيكل Employee بالقيم المقروءة
// TODO: استدعِ InspectStruct مع الموظف واطبع النتيجة
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
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 Pool12مفاهيم OOP المتقدمة
نمط Functional Optionsنمط Builder في Goتسلسل الدوال (Method Chaining)الأسماء المستعارة للأنواع مقابل التعريفاتأساسيات الـ Reflectionنظرة عامة على Code Generationتدرّب بنفسك: مترجم Go عبر الإنترنت