農林漁牧網

您現在的位置是:首頁 > 農業

深入淺出Go語言:反射機制及應用

2022-01-02由 視聽觀察室 發表于 農業

固定模型怎麼變通

本文包括以下部分:

什麼是反射?

如何檢查一個變數及其型別?

反射包

完整程式碼

應該用反射嗎?

現在我們一個一個地討論這些部分。

什麼是反射?

深入淺出Go語言:反射機制及應用

反射是程式在執行時能夠檢查變數、變數值和變數型別的能力。現在你可能不理解什麼意思,但是不要緊。在讀完本文,您將對反射有一個清晰的理解,所以請繼續跟隨我。

檢查一個變數及其型別需要什麼?

在學習反射時每個人碰到第一個問題就是程式執行時為何需要檢查變數型別,程式中的每一個變數都是由我們定義的,並且我們在編譯時就知道它的型別。這在大多數情況下是正確的,但並不總是如此。

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

深入淺出Go語言:反射機制及應用

package

main

import

“fmt”

func

main

() {

i

=

10

fmt

Printf

“%d %T”

i

i

}

在上面的程式碼中,變數i的型別在編譯的時候就知道了,並且在程式碼中列印。沒什麼特別的。

現在我們來看為何在執行時需要知道變數的型別。假設我們想要編寫一個簡單的函式,它接受一個結構體作為引數,並使用它建立一個SQL查詢語句。看以下程式碼:

package

main

import

type

order

struct

{

ordId

int

customerId

int

}

func

main

() {

o

=

order

{

ordId

1234

customerId

567

}

fmt

Println

o

}

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

insert into order values(1234, 567)

實現這個函式很簡單:

深入淺出Go語言:反射機制及應用

package

main

import

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

))

}

createQuery函式使用結構體的ordId和customerId欄位生成sql查詢語句。程式輸出為:

現在將createQuery函式再升級一下。如果我們想要泛化createQuery函式,並讓它在任何結構體上工作,該怎麼辦?我解釋一下我所說的是什麼意思。

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

() {

}

我們的目標是實現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{}型別作為引數型別。為了簡單,我們只處理結構體包含string型別和int型別,但可以擴充套件到任意型別。

要實現createQuery函式對任意結構體型別都生效。唯一的辦法就是在執行時檢查結構體引數的型別,並找到各欄位後,建立查詢語句。這就是反射的作用。下一步,我們將學習如何使用reflect包來實現。

reflect包

reflect包實現了Go執行時反射。反射包能夠識別interface{}變數的底層型別和值。這也是我們需要的。createQuery函式接收interface{}型別引數,需要根據其具體型別和值來建立查詢語句。也是reflect包實現的功能。

在編寫通用createQuery函式之前,我們需要了解反射包中的一些型別和方法。讓我們一個一個來看看。

reflect。Type和reflect。Value

interface{}變數具體型別由reflect。Type表示,而reflect。Value表示其具體值。reflect。TypeOf()函式和reflect。ValueOf()函式分別返回reflect。Type和reflect。Value。這兩種型別是實現createQuery函式的基礎。讓我們編寫一個簡單的事例來理解這兩種型別。

package

main

import

“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{}型別並返回reflect。Type,包含interface{}引數的具體型別。類似地,reflect。ValueOf函式也是接收interface{}型別返回reflect。Value,包含所傳遞的interface{}引數的具體值。

程式碼輸出為:

Type main。orderValue {456 56}

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

reflect。Kind

在反射包中還有一種更重要的型別,稱為Kind。

反射包中的Kind和Type型別似乎很相似,但他們有一個區別,從下面的程式中可以清楚地看到。

package

main

import

type

order

struct

{

ordId

int

customerId

int

}

func

createQuery

q

interface

{}) {

t

=

reflect

TypeOf

q

k

=

t

Kind

()

fmt

Println

t

fmt

Println

“Kind ”

k

}

func

main

() {

o

=

order

{

ordId

456

customerId

56

}

createQuery

o

}

程式輸出為:

Type main。order

Kind struct

我想你現在應該明白兩者之間的區別了。Type表示interface{}的實際型別,在本例中為main。Order,Kind表示特定型別。在這個例子中它是一個struct。

NumField()和Field()方法

NumField()方法返回結構中欄位的數量,Field(i int)方法返回欄位I的reflect。Value。

package

main

import

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()方法

Int和String方法將reflect。Value值提取出來分別是int64和string型別。

package

main

import

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

}

程式碼輸出:

type

int64

value

56

type

string

value

Naveen

完整程式碼

現在我們已經有了足夠的知識來完成createQuery函式,讓我們繼續完成它。

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

}

以上程式碼,我們先檢查傳入的引數是否為struct型別。然後使用reflect。Type的Name()方法獲取結構體名稱。並使用switch—case來判斷各欄位的具體型別,分別處理。

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

程式輸出為:

insert into order values(456,

56

insert into employee values(

“Naveen”

565

“Coimbatore”

90000

“India”

unsupported type

將欄位名新增到輸出查詢中,留給讀者作為練習。請嘗試更改程式列印查詢的格式,

應該使用反射嗎?

在展示了反射的實際應用之後,現在真正的問題來了。應該使用反射嗎?我想引用Rob Pike的話來回答這個問題。

Clear is better than clever。 Reflection is never clear。

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