一些基本方法
本篇不會介紹反射的基本概念和原理等,會從每個常用的方法入手,講解一些基本和進階用法,反射不太適合在業務層使用,因為會幾何倍的降低運行速度,而且用反射做出來的程式健壯度不高,一旦一個環節沒有處理好就會直接panic,影響程式的運行,但是在后臺上使用還是很適合的,可以極大的降低代碼量,從繁復的增刪改查操作和無邊的拋err(面向錯誤編程,太貼切了)中解脫出來,
reflect.TypeOf()
可以獲取任何變數的型別物件,使用該物件可以獲取變數的Name和Kind,Name代表的是變數型別的名稱,Kind代表的是變數的底層型別名稱,以下是兩個典型的例子,
// 系統變數
str := "張三"
reflectType := reflect.TypeOf(str)
fmt.Printf("name: %v kind: %v", reflectType.Name(), reflectType.Kind()) // name: string kind: string
// 自定義變數
type person string
a := person("張三")
reflectType := reflect.TypeOf(a)
fmt.Printf("name: %v kind: %v", reflectType.Name(), reflectType.Kind()) // name: person kind: string
Elem()方法
主要用來獲取指標型別(只能使用在陣列、chan、map、指標、切片幾個型別上)的型別物件
str := "張三"
reflectType := reflect.TypeOf(&str)
reflectElem := reflectType.Elem()
fmt.Printf("name: %v kind: %v", reflectElem.Name(), reflectElem.Kind()) // name: string kind: string
reflect.ValueOf()
可以獲取任意變數的值物件,它的型別是reflect.Value,使用該物件同樣可以獲取變數的Name和Kind,通過獲取Kind可以使用型別斷言獲取變數的值,
這里的
reflect.ValueOf其實作用不大,在實際應用場景中多先使用reflect.ValueOf獲取變數的reflect.Value然后接Interface()方法把變數轉化為Interface{}型別,獲取reflect.Value的方法多采用reflect.TypeOf()加reflect.New()方法,后面實戰部分會有詳細用法,
isNil()
判斷值物件是否為nil,只能對通道、切片、陣列、map、函式、interface等使用,
isValid()
判斷值物件是否為有效值,即非其默認0值,例如數字型別的0,字串型別的"",在實際使用中,如果不對這些值進行處理,可能會直接panic,
reflect.SliceOf()
配合reflect.TypeOf回傳單個型別的切片型別,
str := "張三"
reflectType := reflect.TypeOf(str)
reflectSlice := reflect.SliceOf(reflectType)
fmt.Printf("name: %v kind: %v", reflectSlice.Name(), reflectSlice.Kind()) // name: kind: slice
// 獲取切片中元素的值
a := []int{8, 9, 10}
reflectType := reflect.ValueOf(a)
for i := 0; i < reflectType.Len(); i++ {
fmt.Println(reflectType.Index(i))
}
// 8 9 10
這里注意陣列、指標、切片、map等一些型別是沒有型別名稱的,
reflect.New()
配合reflect.TypeOf實體化一個該型別的值物件,回傳該值物件的指標(想要使用反射設定值,必須使用指標),
str := "張三"
reflectType := reflect.TypeOf(str)
reflectValue := reflect.New(reflectType)
// 設定值
reflectValue.Elem().SetString("李四")
fmt.Printf("value: %v kind: %v", reflectValue.Elem(), reflectValue.Elem().Kind()) // value: 李四 kind: string
reflect.PtrTo()
回傳值物件的指標,
str := "張三"
reflectType := reflect.TypeOf(str)
if reflectType.Kind() != reflect.Ptr {
reflectType = reflect.PtrTo(reflectType)
}
fmt.Printf("value: %v kind: %v", reflectType, reflectType.Kind()) // value: *string kind: ptr
結構體的反射
上面的幾個方法只是開胃菜,真正常用仍然是結構體的反射,業務中各種增刪改查操作都要通過資料庫完成,而資料庫互動使用的都是結構體,這里會先列出一些結構體反射要用到的方法,然后通過一篇后臺公用model類的實戰來完成這篇的內容,
和上面幾個基本方法有關的內容這里就不再贅述,有興趣的可以自己私底下去試試,這里只針對一些結構體的專用方法進行說明,
結構體欄位相關的幾種方法
NumField()
回傳結構體的欄位數量,NumField()使用的物件必須是結構體,否則會panic,
type Student struct {
Name string
Age int
}
a := &Student{
Name: "張三",
Age: 18,
}
reflectValue := reflect.ValueOf(a)
fmt.Println(reflectValue.Elem().NumField()) // 2
Field()
通過欄位的索引獲取欄位的值,從0開始,順序參照結構體定義時的由上到下的順序,
a := &Student{
Name: "張三",
Age: 18,
}
reflectValue := reflect.ValueOf(a)
for i := 0; i < reflectValue.Elem().NumField(); i++ {
fmt.Println(reflectValue.Elem().Field(i))
}
FieldByName()
通過欄位名稱獲取欄位的值,
a := &Student{
Name: "張三",
Age: 18,
}
reflectValue := reflect.ValueOf(a)
fmt.Println(reflectValue.Elem().FieldByName("Name")) // 張三
NumMethod()
回傳結構體的方法數量,
FieldByNameFunc()
根據傳入的匿名函式回傳對應名稱的方法,
Method()
直接通過方法的索引,回傳對應的方法,
MethodByName()
通過方法名稱回傳對應的方法,
以上四個方法相關的函式就不放例子了,通過對應的函式獲取到方法后,使用Call()進行呼叫,其中特別注意的是,呼叫時傳入的引數必須是[]reflect.Value格式的,
實戰篇一:撰寫一個公用的后臺查詢方法
這里用到的資料庫類為gorm本篇不探討其相關知識,如有疑惑,請自行實踐,
首先撰寫model,根目錄下創建檔案夾model,在model檔案夾中創建search.go
// Student 學生
type Student struct {
Name string
Age int
ID int
}
// TableName 表名
func (Student) TableName() string {
return "student"
}
撰寫實作公用方法的介面,根目錄下創建search.go
// SearchModel 搜索介面
type SearchModel interface {
TableName() string
}
// SearchModelHandler 存盤一些查詢程序中的必要資訊
type SearchModelHandler struct {
Model SearchModel
}
// GetSearchModelHandler 獲取處理器
func GetSearchModelHandler(model SearchModel) *SearchModelHandler {
return &SearchModelHandler{
Model: model,
}
}
// Search 查找
func (s *SearchModelHandler) Search() string {
query := db.model(s.Model)
itemPtrType := reflect.TypeOf(s.Model)
if itemPtrType.Kind() != reflect.Ptr {
itemPtrType = reflect.PtrTo(itemPtrType)
}
itemSlice := reflect.SliceOf(itemPtrType)
res := reflect.New(itemSlice)
// 這一步至關重要,雖然Scan方法接收的是一個interface{}型別,但是因為我們這里傳入的SearchModel,如果直接使用s.Model執行傳入會報錯
// 原因在于這里的Scan的interface和我們傳入的model實作的是不同的介面,Scan只認識gorm包中定義的介面型別
err := query.Scan(res.Interface()).Error
if err != nil {
// 這里不要學我
panic("error")
}
ret, _ := json.Marshal(res)
return string(ret)
}
就這樣一個簡單的公用類就誕生了,接下來就是呼叫了,在更目錄下創建main.go
func main() {
handler := GetSearchModelHandler(&model.Student{})
handler.Search()
}
實戰進階篇:為單個表添加上附加資訊
比如我們還有一個班級表,而在回傳學生資訊的時候需要加上班級資訊,這該怎么操作呢,這里我只提供自己的一種思路,如果有更好的建議,請寫在下方的評論里 共同交流,
首先,創建class的結構體,在model檔案夾內創建class.go
// Class 班級
type Class struct {
ID int
Name string
}
// TableName 表名
func (Class) TableName() string {
return "class"
}
然后撰寫一個公用的介面,在model檔案夾下創建檔案additional_api.go
// AdditionalInfo 附加資訊獲取幫助
type AdditionalInfo struct {
FieldName string
Method func(ids []int32) string
}
// MinMapAPI 獲取總內容介面,相當于實戰一中的SearchModel
type MinMapAPI interface {
TableName() string
}
// MinMapInterface 最小資訊獲取介面
type MinMapInterface interface {
TransFields() string
}
上面的方法先定義好,后面有用,然后修改model的內容,打開class.go輸入
// ClassMin 最小班級資訊
type ClassMin struct {
ID int
Name string
}
// TransFields 轉換名稱,填寫你要獲取的欄位的名稱
func (c *ClassMin) TransFields() string {
return "Name"
}
接下來撰寫具體獲取附加資訊的方法,打開additional_api.go,輸入以下內容
// GetMinMap 獲取最小資訊
func GetMinMap(ids []int32, model MinMapAPI, minModel MinMapInterface) string {
// 獲取總資料的切片
modelType := reflect.TypeOf(model)
modelSliceType := reflect.SliceOf(modelType)
res := reflect.New(modelSliceType)
err := db.Model(model).Where("id in (?)", ids).Scan(res.Interface()).Error
if err != nil {
panic("error")
}
minModelType := reflect.TypeOf(minModel).Elem()
resValue := res.Elem()
resLen := resValue.Len()
ret := make(map[int]MinMapInterface, resLen)
for i := 0; i < resLen; i++ {
// 獲取當前下標的資料
item := resValue.Index(i).Elem()
// 獲取要得到的欄位
name := item.FieldByName(minModel.TransFields())
id := item.FieldByName("ID")
// 拼接回傳值
setItem := reflect.New(minModelType)
setItem.Elem().FieldByName("ID").SetInt(int64(id.Interface().(int)))
setItem.Elem().FieldByName(minModel.TransFields()).SetString(name.Interface().(string))
// 查詢出來的內容是具體的model,這里型別斷言轉化回去
ret[id.Interface().(int)] = setItem.Interface().(MinMapInterface)
}
data, _ := json.Marshal(ret)
return string(data)
}
修改student.go,加上獲取附加資料的方法,這里使用了一個匿名函式,既保證了每個model都有其獨有的引數,也保證了代碼的復用性
// AdditionalParams 附加資料引數
func (s *Student) AdditionalParams() map[string]AdditionalInfo {
return map[string]AdditionalInfo{
"class": {
FieldName: "ClassID",
Method: func(ids []int32) string {
return GetMinMap(ids, &Class{}, &ClassMin{})
},
},
}
}
相應的,也要修改search.go,為借口添加上AdditionalParams方法,這里直接貼上search.go的最終代碼以供比對
// SearchModel 搜索介面
type SearchModel interface {
TableName() string
AdditionalParams() map[string]model.AdditionalInfo
}
// SearchModelHandler 存盤一些查詢程序中的必要資訊
type SearchModelHandler struct {
Model SearchModel
ListValue reflect.Value
AdditionalData string
}
// GetSearchModelHandler 獲取處理器
func GetSearchModelHandler(model SearchModel) *SearchModelHandler {
return &SearchModelHandler{
Model: model,
}
}
// Search 查找
func (s *SearchModelHandler) Search() interface{} {
query := db.model(s.Model)
itemPtrType := reflect.TypeOf(s.Model)
if itemPtrType.Kind() != reflect.Ptr {
itemPtrType = reflect.PtrTo(itemPtrType)
}
itemSlice := reflect.SliceOf(itemPtrType)
res := reflect.New(itemSlice)
// 這一步至關重要,雖然Scan方法接收的是一個interface{}型別,但是因為我們這里傳入的SearchModel,如果直接使用s.Model執行傳入會報錯
// 原因在于這里的Scan的interface和我們傳入的model實作的是不同的介面,Scan只認識gorm包中定義的介面型別
err := query.Scan(res.Interface()).Error
if err != nil {
// 這里不要學我
panic("error")
}
s.ListValue = https://www.cnblogs.com/peilanluo/p/res.Elem()
data, _ := json.Marshal(res)
ret := map[string]string {
"list": string(data),
"additional": s.AdditionalData,
}
return ret
}
// GetAdditionalData 獲取附加資訊
func (s *SearchModelHandler) GetAdditionalData() {
additionParams := s.Model.AdditionalParams()
list := s.ListValue
listLen := list.Len()
if len(additionParams) < 1 || list.Len() < 1 {
s.AdditionalData = ""
return
}
additionalIDs := make(map[string][]int)
additionalData := make(map[string]string, len(additionParams))
for i := 0; i < listLen; i++ {
for key, val := range additionParams {
fieldName := val.FieldName
// 判斷Map中的鍵是否已存在
if _, ok := additionalIDs[key]; !ok {
additionalIDs[key] = make([]int, 0, listLen)
}
fields := list.Index(i).Elem().FieldByName(fieldName)
if !fields.IsValid() {
continue
}
additionalIDs[key] = append(additionalIDs[key], fields.Interface().(int))
}
}
for k, v := range additionalIDs {
additionalData[k] = additionParams[k].Method(v)
}
ret, _ := json.Marshal(additionalData)
s.AdditionalData = string(ret)
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/24381.html
標籤:Go
上一篇:[go]指標
