مفهوم بازتاب را در زبان برنامه نویسی Go کاوش کنید و در قابلیت های قدرتمند آن برای تجزیه و تحلیل و دستکاری کد پویا بررسی کنید.
زبان برنامه نویسی Go به دلیل رسا بودنش به طور گسترده ای شناخته شده است. این یک زبان قوی تایپ شده است اما همچنان به برنامهها توانایی دستکاری و بازرسی پویا اشیاء از جمله متغیرها، توابع و انواع را در زمان اجرا میدهد.
بازتاب مکانیزمی است که Go برای انجام این توانایی به کار می گیرد. پس بازتاب چیست و چگونه می توانید بازتاب را در برنامه های Go خود اعمال کنید؟
انعکاس چیست؟
Reflection توانایی یک برنامه برای بررسی متغیرها و ساختار آن و دستکاری آنها در زمان اجرا است.
بازتاب در Go مکانیزمی است که زبان برای دستکاری نوع پویا و شی ارائه می کند. ممکن است لازم باشد اشیا را بررسی کنید، آنها را به روز کنید، متدهای آنها را فراخوانی کنید، یا حتی عملیات بومی انواع آنها را بدون اطلاع از انواع آنها در زمان کامپایل انجام دهید. انعکاس همه اینها را ممکن می کند.
بستههای مختلف در Go از جمله رمزگذاری که شما را قادر میسازد با JSON و fmt کار کنید، برای انجام وظایف خود به شدت به بازتاب زیر هود متکی هستند.
درک پکیج reflect in Go
یادگیری Golang به دلیل معنایی آن و کتابخانه قوی بسته ها و روش هایی که توسعه نرم افزار کارآمد را تسهیل می کند، می تواند چالش برانگیز باشد.
پکیج رفلکس یکی از این بسته های زیاد است. این شامل تمام روش هایی است که برای پیاده سازی بازتاب در برنامه های Go نیاز دارید.
برای شروع کار با بسته reflect، به سادگی می توانید آن را به صورت زیر وارد کنید:
import "reflect"
این بسته دو نوع اصلی را تعریف می کند که پایه و اساس بازتاب را در Go ایجاد می کند: reflect.Type و reflect.Value.
نوع به سادگی یک نوع Go است. reflect.Type رابطی است که از روش های مختلفی برای شناسایی انواع مختلف و بررسی اجزای آنها تشکیل شده است.
تابع برای بررسی نوع هر شی در Go, reflect.TypeOf، هر مقدار (یک رابط{}) را به عنوان تنها آرگومان خود می پذیرد و یک مقدار reflect.Type را برمی گرداند که نشان دهنده نوع پویای شی است.
کد زیر استفاده از reflect.TypeOf را نشان می دهد:
x := "3.142"
y := 3.142
z := 3
typeOfX := reflect.TypeOf(x)
typeOfY := reflect.TypeOf(y)
typeOfZ := reflect.TypeOf(z)
fmt.Println(typeOfX, typeOfY, typeOfZ) // string float64 int
نوع دوم در بسته reflect، reflect.Value می تواند مقداری از هر نوع را در خود جای دهد. تابع reflect.ValueOf هر واسط{} را می پذیرد و مقدار پویا رابط را برمی گرداند.
در اینجا یک مثال نشان می دهد که چگونه از reflect.ValueOf برای بررسی مقادیر بالا استفاده کنید:
valueOfX := reflect.ValueOf(x)
valueOfY := reflect.ValueOf(y)
valueOfZ := reflect.ValueOf(z)
fmt.Println(valueOfX, valueOfY, valueOfZ) // 3.142 3.142 3
برای بررسی انواع و انواع مقادیر، می توانید از روش Kind و Type مانند زیر استفاده کنید:
typeOfX2 := valueOfX.Type()
kindOfX := valueOfX.Kind()
fmt.Println(typeOfX2, kindOfX) // string string
اگرچه نتیجه هر دو فراخوانی تابع یکسان است، اما متمایز هستند. typeOfX2 اساساً همان نوع typeOfX است زیرا هر دو مقادیر Refleks.Type پویا هستند، اما kindOfX یک ثابت است که مقدار آن نوع خاصی از رشته x است.
به همین دلیل است که تعداد محدودی از انواع مانند int، string، float، array و غیره وجود دارد، اما تعداد نامحدودی از انواع وجود دارد، زیرا می تواند چندین نوع تعریف شده توسط کاربر وجود داشته باشد.
یک رابط{} و یک reflect.Value تقریباً به یک شکل کار می کند، آنها می توانند مقادیری از هر نوع را در خود نگه دارند.
تفاوت بین آنها در این است که چگونه یک واسط خالی{} هرگز عملیات و روش های بومی مقداری را که در اختیار دارد نشان نمی دهد. بنابراین اغلب اوقات شما نیاز دارید که نوع پویای مقدار را بدانید و از type assertion برای دسترسی به آن استفاده کنید (یعنی i.(string)، x.(int) و غیره قبل از اینکه بتوانید عملیات را با آن انجام دهید.
در مقابل، یک reflect.Value روشهایی دارد که میتوانید از آنها برای بررسی محتویات و ویژگیهای آن صرف نظر از نوع آن استفاده کنید. بخش بعدی به بررسی عملی این دو نوع می پردازد و نشان می دهد که چگونه در برنامه ها مفید هستند.
پیاده سازی Reflection در برنامه های Go
انعکاس بسیار گسترده است و می تواند در یک برنامه در هر نقطه از زمان استفاده شود. در زیر چند مثال عملی وجود دارد که استفاده از بازتاب را در برنامه ها نشان می دهد:
- بررسی عمق برابری: بسته reflect تابع DeepEqual را برای بررسی مقادیر دو شی در عمق برای برابری ارائه می دهد. به عنوان مثال، اگر تمام فیلدهای متناظر آنها دارای انواع و مقادیر یکسان باشند، دو ساختار عمیقاً برابر هستند. در اینجا یک کد مثال آورده شده است: // برابری عمیق دو آرایه arr1 := […]int{1, 2, 3} arr2 := […]int{1, 2, 3} fmt.Println(reflect. DeepEqual(arr1، arr2)) // true
- کپی برش ها و آرایه ها: همچنین می توانید از Go reflection API برای کپی کردن محتویات یک برش یا آرایه در دیگری استفاده کنید. به این صورت است: slice1 := []int{1, 2, 3} slice2 := []int{4, 5, 6} reflect.Copy(reflect.ValueOf(slice1), reflect.ValueOf(slice2)) fmt.Println (برش1) // [4 5 6]
- تعریف توابع عمومی: زبانهایی مانند TypeScript یک نوع عمومی ارائه میکنند که میتوانید از آن برای نگهداری متغیرها از هر نوع استفاده کنید. در حالی که Go با یک نوع عمومی داخلی ارائه نمی شود، می توانید از بازتاب برای تعریف توابع عمومی استفاده کنید. برای مثال: // نوع هر مقدار را چاپ کنید func printType(x reflect.Value) { fmt.Println(“Value type:”، x.Type()) }
- دسترسی به تگ های ساختار: از تگ ها برای افزودن ابرداده به فیلدهای ساختار Go استفاده می شود و بسیاری از کتابخانه ها از آنها برای تعیین و دستکاری رفتار هر فیلد استفاده می کنند. شما فقط می توانید به تگ های struct با بازتاب دسترسی داشته باشید. کد نمونه زیر این را نشان می دهد: فیلد User struct { Name string `json:”name” الزامی است:”true”` } user := User{“John”} فیلد، ok := reflect.TypeOf(user).Elem() FieldByName(“Name”) if !ok { fmt.Println(“Field not found) } // چاپ همه برچسبها و مقدار “لازم” fmt.Println(field.Tag, field.Tag.Get(“required “)) // json:”name” مورد نیاز: “true” true
- بازتاب در رابط ها: همچنین می توان بررسی کرد که آیا یک مقدار یک رابط را پیاده سازی می کند یا خیر. این می تواند زمانی مفید باشد که نیاز به انجام برخی لایه های اضافی از اعتبارسنجی بر اساس الزامات و اهداف برنامه خود داشته باشید. کد زیر نشان میدهد که چگونه بازتاب به شما کمک میکند رابطها را بازرسی کنید و ویژگیهای آنها را تعیین کنید: var i interface{} = 3.142 typeOfI := reflect.TypeOf(i) stringerInterfaceType:= reflect.TypeOf(new(fmt.Stringer)) // بررسی کنید آیا i رابط stringer impl := typeOfI.Implements(stringerInterfaceType.Elem()) fmt.Println(impl) // false را پیاده سازی می کند
// deep equality of two arrays
arr1 := [...]int{1, 2, 3}
arr2 := [...]int{1, 2, 3}
fmt.Println(reflect.DeepEqual(arr1, arr2)) // true
slice1 := []int{1, 2, 3}
slice2 := []int{4, 5, 6}
reflect.Copy(reflect.ValueOf(slice1), reflect.ValueOf(slice2))
fmt.Println(slice1) // [4 5 6]
// print the type of any value
func printType(x reflect.Value) {
fmt.Println("Value type:", x.Type())
}
type User struct {
Name string `json:"name" required:"true"`
}
user := User{"John"}
field, ok := reflect.TypeOf(user).Elem().FieldByName("Name")
if !ok {
fmt.Println("Field not found")
}
// print all tags, and value of "required"
fmt.Println(field.Tag, field.Tag.Get("required"))
// json:"name" required:"true" true
var i interface{} = 3.142
typeOfI := reflect.TypeOf(i)
stringerInterfaceType := reflect.TypeOf(new(fmt.Stringer))
// check if i implements the stringer interface
impl := typeOfI.Implements(stringerInterfaceType.Elem())
fmt.Println(impl) // false
مثال های بالا راه هایی هستند که می توانید از بازتاب در برنامه های Go واقعی خود استفاده کنید. پکیج رفلکس بسیار قوی است و می توانید در مستندات رسمی Go reflect درباره قابلیت های آن بیشتر بدانید.
زمان استفاده از بازتاب و تمرین های توصیه شده
ممکن است چندین سناریو وجود داشته باشد که بازتاب ممکن است ایده آل به نظر برسد، اما توجه به این نکته مهم است که بازتاب معاوضه های خاص خود را دارد و زمانی که به درستی استفاده نمی شود، می تواند بر برنامه تأثیر منفی بگذارد.
در اینجا مواردی وجود دارد که باید در مورد بازتاب توجه کرد:
- فقط زمانی باید از انعکاس استفاده کنید که نتوانید از قبل نوع یک شی را در برنامه خود تعیین کنید.
- انعکاس می تواند عملکرد برنامه شما را کاهش دهد، بنابراین باید از استفاده از آن برای عملیات های حیاتی خودداری کنید.
- انعکاس ممکن است بر خوانایی کد شما نیز تأثیر بگذارد، بنابراین میخواهید از پرتاب آن به همه جا اجتناب کنید.
- با بازتاب، خطاها در زمان کامپایل ثبت نمی شوند، بنابراین ممکن است برنامه خود را در معرض خطاهای بیشتری در زمان اجرا قرار دهید.
در صورت نیاز از Reflection استفاده کنید
Reflection در بسیاری از زبان ها از جمله C# و JavaScript موجود است و Go به خوبی API را به خوبی پیاده سازی می کند. مزیت اصلی بازتاب در Go این است که وقتی از قابلیت کتابخانه استفاده می کنید می توانید با کد کمتری مشکلات را حل کنید.
با این حال، ایمنی نوع برای اطمینان از کد قابل اعتماد بسیار مهم است و سرعت عامل مهم دیگری برای تجربه کاربری روان است. به همین دلیل است که شما باید فقط پس از سنجیدن گزینه های خود از بازتاب استفاده کنید. و سعی کنید کد خود را خوانا و بهینه نگه دارید.