GO-反射簡介
阿新 • • 發佈:2018-12-09
反射概述
- 通常的程式邏輯是:編碼時先寫好劇本,執行時按照寫好的劇本演
- 何時建立一個什麼例項,給哪個屬性賦什麼值,然後呼叫它的哪個方法都毫釐不差
- 但能否在執行時動態地生成“劇本”呢?
- 根據具體的業務需要見機行事,動態地生成一個不知道具體型別會是什麼的例項,動態地訪問一個無法提前預知的屬性或方法
- 答案是可以的,這便引出了今天的主角——反射
應用場景舉例:匯出商品列表到Excel
- 需求是:不管使用者在介面上看到什麼商品列表,當它捅一下匯出按鈕,就將該商品的所有屬性和值寫出為檔案;
- 本例的難點是:我們無法預知使用者會選擇匯出什麼型別的商品資料、它有哪些屬性,也就無法相應地去建立Excel資料表的列(讀寫Excel有相應的庫,你也可以選擇寫出為文字檔案、寫出到資料庫表中等等)
- 因為商品的種類太多,如果用正射去做,那麼有多少商品型別我們就要寫多少個switch或if分支,然後在每一個分支里根據當前分支的具體商品型別去構造相應的Excel資料列,這顯然是蹩腳並且很難維護和擴充套件的
- 而通過反射來做就易如反掌了,管你來的是什麼商品例項,立即動態解析得到其類別、所有屬性名值,然後根據屬性名去建立Excel的列,根據屬性值去做資料的填充和寫入
定義結構體
type Human struct { Name string "姓名" Age int "年齡" } func (h *Human) GetName() string { fmt.Println("GetName called",h.Name) return h.Name } func (h *Human) SetName(name string) { fmt.Println("SetName called:", name) h.Name = name } func (h *Human) Eat(food string, grams int) (power int) { fmt.Println("Eat called:", food, grams) return 5 * grams }
反射功能一覽
func main() { //建立物件(在實際開發中可以是任意未知型別) h := Human{"bill", 60} //獲取任意物件的型別和值 getObjTypeValue(h) //獲得任意物件的所有屬性 getObjFields(h) //獲取任意物件的所有方法 getObjMethods(&h) //修改物件任意屬性的值 modifyFieldValue(&h) //動態呼叫物件的任意方法 callMethods(&h) fmt.Println("after:h=", h) }
獲取任意物件的型別和值
func getObjTypeValue(obj interface{}) {
oType := reflect.TypeOf(obj)
oKind := oType.Kind()
fmt.Println(oType, oKind)
oValue := reflect.ValueOf(obj)
fmt.Println(oValue)
}
獲得任意物件的所有屬性
func getObjFields(obj interface{}) {
oType := reflect.TypeOf(obj)
oValue := reflect.ValueOf(obj)
fieldsCount := oType.NumField()
for i := 0; i < fieldsCount; i++ {
//從物件值中獲取第i個屬性的值,進而值的“正射”形式
fValue := oValue.Field(i).Interface()
structField := oType.Field(i)
fmt.Println(structField.Name, structField.Type, structField.Tag, fValue)
}
}
獲取任意物件的所有方法
func getObjMethods(obj interface{}) {
oType := reflect.TypeOf(obj)
fmt.Println(oType.NumMethod())
for i := 0; i < oType.NumMethod(); i++ {
method := oType.Method(i)
fmt.Println(method.Name, method.Type)
}
}
修改物件任意屬性的值
func modifyFieldValue(objPtr interface{}) {
//得到【指標的Value】
oPtrValue := reflect.ValueOf(objPtr)
//得到【指標所指向的值(結構體)的Value】
oValue := oPtrValue.Elem()
fmt.Println(oValue)
//遍歷所有屬性的值
for i := 0; i < oValue.NumField(); i++ {
//根據序號拿到【屬性的Value】
fValue := oValue.Field(i)
//拿到屬性值的原生型別
fKind := fValue.Kind()
//fmt.Println(fKind)
//根據不同的原生型別設定為不同的值
switch fKind {
case reflect.String:
fValue.SetString("張全蛋")
case reflect.Int:
fValue.SetInt(99)
case reflect.Bool:
fValue.SetBool(false)
default:
fmt.Println("設定為其他值...")
}
}
}
動態呼叫物件的任意方法
func callMethods(objPtr interface{}) {
//要通過物件的oType拿取方法的引數列表(oType.In(i))
oType := reflect.TypeOf(objPtr)
//要通過物件的oValue拿取方法的具體實現(methodValue)
oValue := reflect.ValueOf(objPtr)
//根據方法的數量進行遍歷
for i := 0; i < oType.NumMethod(); i++ {
//預定義要傳值的引數切片[]Value
args := make([]reflect.Value, 0)
//從物件的oType拿到當前的方法的methodType
methodType := oType.Method(i).Type
//根據引數個數進行遍歷
//為每個引數亂懟一個同種型別反射值,丟入引數列表
//內層迴圈走完時,當前方法的引數列表就形成了
for j:=0;j<methodType.NumIn();j++{
//從方法的methodType獲取當前引數artType
artType := methodType.In(j)
//再獲取引數型別artType的原生型別
argKind := artType.Kind()
//根據不同的引數原生型別亂懟相同型別的值
switch argKind {
case reflect.String:
//獲取字串"一坨翔"的反射值Value,丟入引數列表
args = append(args,reflect.ValueOf("一坨翔"))
case reflect.Int:
args = append(args,reflect.ValueOf(100))
case reflect.Bool:
args = append(args,reflect.ValueOf(false))
}
}
//從物件值oValue中獲取當前方法值methodValue
methodValue := oValue.Method(i)
//使用引數列表+方法值,反射呼叫當前方法
methodValue.Call(args)
}
}