1. 程式人生 > >golang教程之反射

golang教程之反射

文章目錄

反射

原文:https://golangbot.com/reflection/

在這裡插入圖片描述
反射是Go的高階主題之一。

什麼是反射?

反射是程式在執行時檢查其變數和值並找到其型別的能力。你可能不明白這意味著什麼,但沒關係。在本教程結束時,您將清楚地瞭解反射。

檢查變數並找到其型別需要什麼?

在學習反射時,任何人都會得到的第一個問題是,為什麼我們甚至需要檢查變數並在執行時找到它的型別,當我們的程式中的每個變數都由我們定義時,我們在編譯時就知道它的型別。嗯,大部分時候都是如此,但並非總是如此。

讓我解釋一下我的意思。我們來寫一個簡單的程式。

package main

import (  
    "fmt"
)

func main() {  
    i := 10
    fmt.Printf("%d %T", i, i)
}

在上面的程式中,i的型別在編譯時是已知的,我們在下一行列印它。 這裡沒什麼神奇的。

現在讓我們瞭解在執行時知道變數型別的必要性。假設我們想編寫一個簡單的函式,它將struct作為引數,並使用它建立一個SQL插入查詢。

思考以下程式,

package main

import (  
    "fmt"
)

type order struct {  
    ordId      int
    customerId int
}

func main() {  
    o := order{
        ordId:      1234,
        customerId: 567,
    }
    fmt.Println(o)
}

我們需要編寫一個函式,它將上面程式中的struct o作為引數並返回以下SQL插入查詢,

insert into order values(1234, 567)  

這個功能很容易編寫。 讓我們現在就這樣做。

package main

import (  
    "fmt"
)

type order struct {  
    ordId      int
    customerId int
}

func createQuery(o order) string {  
    i := fmt.Sprintf("insert into order values(%d, %d)", o.ordId, o.customerId)
    return i
}

func main() {  
    o := order{
        ordId:      1234,
        customerId: 567,
    }
    fmt.Println(createQuery(o))
}

第12行中的createQuery函式使用oordIdcustomerId欄位建立插入查詢。 該程式將輸出,

insert into order values(1234, 567)  

現在讓我們將查詢建立者提升到一個新的水平。 如果我們想要概括我們的查詢建立者並使其適用於任何結構,該怎麼辦? 讓我解釋一下我使用程式的意思。

package main

type order struct {  
    ordId      int
    customerId int
}

type employee struct {  
    name string
    id int
    address string
    salary int
    country string
}

func createQuery(q interface{}) string {  
}

func main() {

}

我們的目標是在第16行完成createQuery函式。 以上程式中,以便它將任何結構作為引數,並基於結構欄位建立插入查詢。

例如,如果我們傳遞下面的結構,

o := order {  
    ordId: 1234,
    customerId: 567
}

我們的createQuery函式應該返回,

insert into order values (1234, 567)  

同樣,如果我們通過

 e := employee {
        name: "Naveen",
        id: 565,
        address: "Science Park Road, Singapore",
        salary: 90000,
        country: "Singapore",
    }

它應該回來,

insert into employee values("Naveen", 565, "Science Park Road, Singapore", 90000, "Singapore")  

由於createQuery函式應該與任何結構一起使用,因此它將interface{}作為引數。為簡單起見,我們只處理包含stringint型別欄位的結構,但這可以擴充套件為任何型別。

createQuery函式應該適用於任何結構。編寫此函式的唯一方法是檢查在執行時傳遞給它的struct引數的型別,找到它的欄位然後建立查詢。這是反射有用的地方。在本教程的後續步驟中,我們將學習如何使用reflect包實現此目的。

反射包

反射包在Go中實現執行時反射。反射包有助於識別底層具體型別和interface {}變數的值。這正是我們所需要的。 createQuery函式採用interface {}引數,需要根據interface {}引數的具體型別和值建立查詢。這正是反射包有助於實現的目的。

在編寫我們的通用查詢生成器程式之前,我們需要首先了解反射包中的一些型別和方法。讓我們逐一看看它們。

reflect.Type和reflect.Value

interface{}的具體型別由reflect.Type表示,底層值由reflect.Value表示。有兩個函式reflect.TypeOf()reflect.ValueOf(),它們分別返回reflect.Typereflect.Value。這兩種型別是建立查詢生成器的基礎。讓我們寫一個簡單的例子來理解這兩種型別。

package main

import (  
    "fmt"
    "reflect"
)

type order struct {  
    ordId      int
    customerId int
}

func createQuery(q interface{}) {  
    t := reflect.TypeOf(q)
    v := reflect.ValueOf(q)
    fmt.Println("Type ", t)
    fmt.Println("Value ", v)


}
func main() {  
    o := order{
        ordId:      456,
        customerId: 56,
    }
    createQuery(o)

}

在上面的程式中,createQuery函式以 interface{} 作為引數。 函式reflect.TypeOf將 interface{} 作為引數,並返回包含傳遞的 interface{} 引數的具體型別的reflect.Type。 類似,reflect.ValueOf函式interface {}作為引數並返回reflect.Value,其中包含傳遞的interface {}引數的基礎值。

以上程式列印,

Type  main.order  
Value  {456 56}  

從輸出中,我們可以看到程式列印具體型別和介面的值。

reflect.Kind

反射包中有一個重要的型別名為Kind

反射包中的KindType可能看起來相似,但它們之間存在差異,這將從下面的程式中清楚地看出。

package main

import (  
    "fmt"
    "reflect"
)

type order struct {  
    ordId      int
    customerId int
}

func createQuery(q interface{}) {  
    t := reflect.TypeOf(q)
    k := t.Kind()
    fmt.Println("Type ", t)
    fmt.Println("Kind ", k)


}
func main() {  
    o := order{
        ordId:      456,
        customerId: 56,
    }
    createQuery(o)

}

上面的程式輸出,

Type  main.order  
Kind  struct 

我想你現在會清楚兩者之間的差異。 Type表示 interface{}的實際型別,在這種情況下,main.OrderKind表示型別的特定種類。 在這種情況下,它是一個結構。

NumField()和Field()方法

NumField()方法返回結構中的欄位數,Field(i int)方法返回第i個欄位的reflect.Value

package main

import (  
    "fmt"
    "reflect"
)

type order struct {  
    ordId      int
    customerId int
}

func createQuery(q interface{}) {  
    if reflect.ValueOf(q).Kind() == reflect.Struct {
        v := reflect.ValueOf(q)
        fmt.Println("Number of fields", v.NumField())
        for i := 0; i < v.NumField(); i++ {
            fmt.Printf("Field:%d type:%T value:%v\n", i, v.Field(i), v.Field(i))
        }
    }

}
func main() {  
    o := order{
        ordId:      456,
        customerId: 56,
    }
    createQuery(o)
}

在上面的程式中,我們首先檢查q是否是 Kind struct ,因為NumField方法僅適用於struct。該程式輸出,

Number of fields 2  
Field:0 type:reflect.Value value:456  
Field:1 type:reflect.Value value:56  

Int()和String()方法

IntString方法有助於將reflect.Value分別提取為int64string

package main

import (  
    "fmt"
    "reflect"
)

func main() {  
    a := 56
    x := reflect.ValueOf(a).Int()
    fmt.Printf("type:%T value:%v\n", x, x)
    b := "Naveen"
    y := reflect.ValueOf(b).String()
    fmt.Printf("type:%T value:%v\n", y, y)

}

在上面的程式中,我們將reflect.Value提取為int64並在第13行中把它作為字串提取出來。 這個程式列印,

type:int64 value:56  
type:string value:Naveen  

完成程式

既然我們有足夠的知識來完成我們的查詢生成器,那就讓我們繼續吧。

package main

import (  
    "fmt"
    "reflect"
)

type order struct {  
    ordId      int
    customerId int
}

type employee struct {  
    name    string
    id      int
    address string
    salary  int
    country string
}

func createQuery(q interface{}) {  
    if reflect.ValueOf(q).Kind() == reflect.Struct {
        t := reflect.TypeOf(q).Name()
        query := fmt.Sprintf("insert into %s values(", t)
        v := reflect.ValueOf(q)
        for i := 0; i < v.NumField(); i++ {
            switch v.Field(i).Kind() {
            case reflect.Int:
                if i == 0 {
                    query = fmt.Sprintf("%s%d", query, v.Field(i).Int())
                } else {
                    query = fmt.Sprintf("%s, %d", query, v.Field(i).Int())
                }
            case reflect.String:
                if i == 0 {
                    query = fmt.Sprintf("%s\"%s\"", query, v.Field(i).String())
                } else {
                    query = fmt.Sprintf("%s, \"%s\"", query, v.Field(i).String())
                }
            default:
                fmt.Println("Unsupported type")
                return
            }
        }
        query = fmt.Sprintf("%s)", query)
        fmt.Println(query)
        return

    }
    fmt.Println("unsupported type")
}

func main() {  
    o := order{
        ordId:      456,
        customerId: 56,
    }
    createQuery(o)

    e := employee{
        name:    "Naveen",
        id:      565,
        address: "Coimbatore",
        salary:  90000,
        country: "India",
    }
    createQuery(e)
    i := 90
    createQuery(i)

}

第22行,我們首先檢查傳遞的引數是否是結構。 我們使用Name()方法從reflect.Type獲取結構的名稱。 在下一行中,我們使用t並開始建立查詢。

第28行檢查當前欄位是否為reflect.Int,如果是這種情況,我們使用Int()方法將該欄位的值提取為int64if else語句用於處理邊緣情況。 請新增日誌以瞭解為何需要它。 類似的邏輯用於提取第34行中的字串。

我們還添加了一些檢查,以防止在將不支援的型別傳遞給createQuery函式時程式崩潰。我建議在適當的位置新增日誌並檢查其輸出以更好地理解該程式。

這個程式列印,

insert into order values(456, 56)  
insert into employee values("Naveen", 565, "Coimbatore", 90000, "India")  
unsupported type  

我會把它作為練習讓讀者將欄位名稱新增到輸出查詢中。 請嘗試更改程式以列印格式的查詢,

insert into order(ordId, customerId) values(456, 56)  

應該使用反射嗎?

展示了反射的實際用途,現在出現了真正的問題。 你應該使用反射嗎? 我想引用Rob Pike關於使用反射來回答這個問題的諺語。

Clear is better than clever.Reflection is never clear.

反射是Go中一個非常強大和先進的概念,應該謹慎使用。 使用反射編寫清晰且可維護的程式碼非常困難。 應儘可能避免使用,並且只有在絕對必要時才應使用。