1. 程式人生 > >GO-反射簡介

GO-反射簡介

反射概述

  • 通常的程式邏輯是:編碼時先寫好劇本,執行時按照寫好的劇本演
  • 何時建立一個什麼例項,給哪個屬性賦什麼值,然後呼叫它的哪個方法都毫釐不差
  • 但能否在執行時動態地生成“劇本”呢?
  • 根據具體的業務需要見機行事,動態地生成一個不知道具體型別會是什麼的例項,動態地訪問一個無法提前預知的屬性或方法
  • 答案是可以的,這便引出了今天的主角——反射

應用場景舉例:匯出商品列表到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)
    }
}