上篇說了下反射該怎麼用,如今咱們來看一看使用反射的實際狀況,深刻理解一下golang
這篇由於是實踐篇,因此有大量的代碼示例來進行演示,由於只是演示反射的使用,因此對一些複雜的錯誤機制沒作處理json
反射自己並不難,看懂了上一章反射究竟是幹嗎用的,何時用,這一章其實很是好懂設計模式
說到底就是將reflect
包提供給咱們的方法,進行一些組合使用罷了,說土一點就是調用下API數組
沒看上篇的能夠先看看golang的反射與實踐(上)微信
好了,我們開始進行實踐app
先把咱們的準備工做作好,先定義一個Struct
,再給這個結構體加上一些方法函數
// Employee 員工
type Employee struct {
Name string `json:"emp_name"`
Age int `json:"emp_age"`
Sex int
}
// GetSum 返回兩數之和
func (e *Employee) GetSum(n1, n2 int) int {
return n1 + n2
}
// Set 接受值,給結構體e賦值
func (e *Employee) Set(name string, age, sex int) {
e.Name = name
e.Age = age
e.Sex = sex
}
// Print 打印結構體*Employee
func (e *Employee) Print() {
log.Print("======Start======")
log.Print(e)
log.Print("======End======")
}
複製代碼
隨便給這個結構體寫了幾個方法,咱們主要是看,咱們如何使用反射在運行時對變量進行一個操做性能
先來看個常規用法學習
// GetStruct 獲取結構體的字段及tag
func GetStruct(i interface{}) {
rType := reflect.TypeOf(i)
rVal := reflect.ValueOf(i)
kd := rVal.Kind()
// 若是是傳進來的是指針類型
// 則獲取指針值
if kd == reflect.Ptr {
rType = rType.Elem()
rVal = rVal.Elem()
kd = rVal.Kind()
}
if kd != reflect.Struct {
log.Panicf("Kind is %v not struct ", kd)
}
// 獲取結構體的字段數
sNum := rVal.NumField()
log.Printf("Struct has %v fields ", sNum)
// 遍歷結構體的全部字段
for i := 0; i < sNum; i++ {
log.Printf("Field %d value is %v", i, rVal.Field(i))
// 獲取Struct的tag,使用Type類型獲取
tag := rType.Field(i).Tag.Get("json")
if tag == "" {
log.Printf("Field %d hasn't tag %v ", i, tag)
continue
}
log.Printf("Field %d tag is %v ", i, tag)
}
}
複製代碼
咱們定義一個方法GetStruct(i interface{})
,由於入參是interface{}
類型,因此這個方法能夠接收並處理全部的數據類型。這就是反射的牛逼之處了測試
遺憾的是,反射的性能比較低。後面我們對性能進行分析時再拿出來聊聊
測試用例以下
func TestGetStruct(t *testing.T) {
emp := &Employee{}
emp.Set("鬧鬧", 99, 0)
GetStruct(emp)
}
複製代碼
執行結果以下圖所示
這個函數接受的參數是interface
,也就是說,經過這個函數,無論入參傳遞了什麼樣的結構體,咱們能夠知道這個結構體有什麼標籤,有幾個方法
獲取tag
標籤的用處就是對咱們的結構體進行序列化時使用,將結構體的字段名變成咱們須要的別名
想深刻了解的童鞋,能夠參考下encoding/json
包的使用方式
// CallMethod 調用結構體方法
// i : 傳入的struct
// methodByName : 調用結構體的方法名
func CallMethod(i interface{}, methodByName string) {
rVal := reflect.ValueOf(i)
rType := reflect.TypeOf(i)
log.Printf("Type is %v Kind is %v", rType, rType.Kind())
// 獲取結構體有多少個方法
numOfMethod := rVal.NumMethod()
log.Printf("Struct has %d method", numOfMethod)
// 聲明Value數組
var params []reflect.Value
// 聲明一個Value類型,用於接收方法
var method reflect.Value
if methodByName == "GetSum" {
// 調用方法時的參數
params = append(params, reflect.ValueOf(10))
params = append(params, reflect.ValueOf(88))
}
if methodByName == "Set" {
// 調用方法時的參數
params = append(params, reflect.ValueOf("鬧鬧吃魚"))
params = append(params, reflect.ValueOf(18))
params = append(params, reflect.ValueOf(0))
}
// 獲取方法
method = rVal.MethodByName(methodByName)
if !method.IsValid() {
// 若是結構體不存在此方法,輸出Panic
log.Panic("Method is invalid")
}
result := method.Call(params)
if len(result) > 0 {
// 若是函數存在返回值,則打印第一條
log.Println("Call result is ", result[0])
}
}
複製代碼
這裏值得注意一點的就是,咱們經過反射的Call
去調用函數,傳入的參數的類型是reflect.Value
類型,並非咱們定義函數時的int
類型
因此在調用函數時傳入的參數須要進行一個類型轉換
給大家附上測試用例,大家能夠本身調試跑跑,會發現,無論你傳的結構體的字段是什麼,我都進行統一處理了
func TestCallMethod(t *testing.T) {
emp := &Employee{}
emp.Set("鬧鬧", 99, 0)
emp.Print()
CallMethod(emp, "Set")
emp.Print()
}
複製代碼
// ModifyField 修改字段值
func ModifyField(i interface{}, filedName string) {
rVal := reflect.ValueOf(i)
filed := rVal.Elem().FieldByName(filedName)
if !filed.IsValid() {
log.Panic("filedName is invalid")
}
filed.SetString("鬧鬧")
}
複製代碼
運行時修改結構體的字段,主要就是作到一個通用性,好比上述的例子,不論是什麼結構體
依然附上測試用例
func TestModifyField(t *testing.T) {
emp := &Employee{}
ModifyField(emp, "Name")
}
複製代碼
無論傳入的結構體是什麼,只要包含了filedName
(咱們指定的字段名),咱們就能夠對其進行值的更改
假如咱們有100個結構體,須要對name
字段進行修改,經過反射的機制,咱們代碼的耦合度將大大的下降
// Bridge 適配器
// 能夠實現調用任意函數
func Bridge(call interface{}, args ...interface{}) {
var (
function reflect.Value
inValue []reflect.Value
)
n := len(args)
// 將參數轉換爲Value類型
inValue = make([]reflect.Value, n)
for i := 0; i < n; i++ {
inValue[i] = reflect.ValueOf(args[i])
}
// 得到函數的Value類型
function = reflect.ValueOf(call)
// 傳參,調用函數
function.Call(inValue)
}
複製代碼
寫了個測試用例,函數是咱們在調用Bridge
前就已經定義好了
func TestBridge(t *testing.T) {
call1 := func(v1, v2 int) {
log.Println(v1, v2)
}
call2 := func(v1, v2 int, str string) {
log.Println(v1, v2, str)
}
Bridge(call1, 1, 2)
Bridge(call2, 2, 3, "callTest")
}
複製代碼
兩個函數是不一樣的函數,可是均可以經過Bridge
進行執行
適配器有什麼用呢?若是不知道的童鞋,能夠去看看設計模式「適配器模式」
由於本篇幅只是說如何在實戰中應用反射,因此這裏就不講解設計模式了
// CreateStruct 使用反射建立結構體
// 並給結構體賦值
func CreateStruct(i interface{}) *Employee {
var (
structType reflect.Type
structValue reflect.Value
)
// 獲取傳入結構體指向的Type類型
structType = reflect.TypeOf(i).Elem()
// 建立一個結構體
// structValue持有一個指向類型爲Type的新申請的指針
structValue = reflect.New(structType)
// 轉換成咱們要建立的結構體
modle := structValue.Interface().(*Employee)
// 取得structValue指向的值
structValue = structValue.Elem()
// 給結構體賦值
structValue.FieldByName("Name").SetString("鬧鬧吃魚")
structValue.FieldByName("Age").SetInt(100)
return modle
}
複製代碼
使用方式就看看測試用例
func TestCreateStruct(t *testing.T) {
emp := &Employee{
Name: "NaoNao",
Age: 18,
Sex: 1,
}
emp.Print()// &{NaoNao 18 1}
newEmp := CreateStruct(emp)
newEmp.Print()// &{鬧鬧吃魚 100 0}
}
複製代碼
可能你會問,CreateStruct
的入參不是interface{}
嗎?可爲何我傳一個任意的結構體,卻要給返回一個指定的結構體呢?就不能我傳什麼結構體進去,就返回什麼結構體出來嗎?
理想老是豐滿的,現實倒是很是骨感,雖然咱們反射的方法實現,都是將入參寫爲interface{}
,但使用反射並非意味着咱們必定就寫了一個萬能的程序
還記得上一篇提到的,變量
與reflect.Value
之間該如何轉換嗎?
我們再複習一下:變量<------>interface{}<------>reflect.Value
咱們不論是把Value
轉爲結構體,仍是轉爲基本類型,咱們都須要在編譯前肯定轉換後的類型
換句話說,只要咱們在運行時牽扯到類型的轉換,咱們都須要各類if
來判斷是否能轉換成咱們須要的類型
本文以大量的代碼實現來闡述反射該怎麼用,說實話,挺無聊的
寫這篇文章的目的就是讓你拿電腦上去編譯跑跑,或者何時想到要用反射了,能夠拿出來瞅瞅,看看什麼地方須要用到反射,反射又能夠幹什麼