您好,登錄后才能下訂單哦!
本文小編為大家詳細介紹“怎么利用Golang泛型提高編碼效率”,內容詳細,步驟清晰,細節處理妥當,希望這篇“怎么利用Golang泛型提高編碼效率”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。
雖然標準庫里面已經提供了大量的工具函數,但是這些工具函數都沒有使用泛型實現,為了提高使用體驗,我們可以使用泛型進行實現。
比如數值算法里很經典的math.Max()
、math.Min()
都是float64
類型的,但是很多時候我們使用的是int
、int64
這些類型,在Golang引入泛型之前,我們經常像下面這樣根據類型實現,產生大量模板代碼:
func MaxInt(a, b int) int { if a > b { return a } return b } func MaxInt64(a, b int64) int64 { if a > b { return a } return b } // ...其他類型
而使用泛型則我們只需要一個實現:
func Max[T constraints.Ordered](a, b T) T { if a > b { return a } return b }
其中constraints.Ordered
表示可排序類型,也就是可以使用三路運算符的類型[>, =, <]
,包含了所有數值類型和string
。可以通過go get golang.org/x/exp
引入。
代碼地址
其他的像json解析、參數校驗、slices等也可以通過泛型進行實現。
Golang自帶的泛型容器有slices和map,這兩個數據結構其實可以完成大部分工作了,但是有時候我們可能還需要其他的數據結構,比如說優先級隊列、鏈表等。
雖然Golang在container
包下有heap
、list
和ring
三個數據結構,但說實話使用起來不是很方便,特別是元素類型全是interface{}
,使用這些結構就需要各種類型轉換。因此我們可以簡單的拷貝這些代碼,然后使用泛型進行改造,比如heap:
我們不但使用泛型進行實現,還把heap默認改為使用slice是實現,這樣只需要實現一個LessFunc,而不是5個。
package heap type LessFunc[T any] func(e1 T, e2 T) bool type Heap[T any] struct { h []T lessFunc LessFunc[T] } func New[T any](h []T, lessFunc LessFunc[T]) *Heap[T] { heap := &Heap[T]{ h: h, lessFunc: lessFunc, } heap.init() return heap } // 移除堆頂元素 func (h *Heap[T]) Pop() T { n := h.Len() - 1 h.swap(0, n) h.down(0, n) return h.pop() } // 獲取堆頂元素 func (h *Heap[T]) Peek() T { return h.h[0] } // 添加元素到堆 func (h *Heap[T]) Push(x T) { h.push(x) h.up(h.Len() - 1) }
代碼地址
其他的數據結構還包括list、set、pqueue等。
在后臺業務代碼里面,我們經常會有很多個業務處理函數,每個業務處理函數我們基本都會通過一些代碼封裝成一個HTTP接口,這里其實基本上都是模板代碼,比如說對于一個使用gin實現的HTTP
服務,每個接口我們都需要進行以下處理:
指定HTTP方法、URL
鑒權
參數綁定
處理請求
處理響應
可以發現,參數綁定、處理響應幾乎都是一樣模板代碼,鑒權也基本上是模板代碼(當然有些鑒權可能比較復雜)。
因此我們可以編寫一個泛型模板,把相同的部分抽取出來,用戶只需要實現不同接口有差異的指定HTTP方法、URL和處理請求邏輯即可:
// 處理請求 func do[Req any, Rsp any, Opt any](reqFunc ReqFunc[Req], serviceFunc ServiceFunc[Req, Rsp], serviceOptFunc ServiceOptFunc[Req, Rsp, Opt], opts ...Opt) gin.HandlerFunc { return func(c *gin.Context) { // 參數綁定 req, err := BindJSON[Req](c) if err != nil { return } // 進一步處理請求結構體 if reqFunc != nil { reqFunc(c, req) } var rsp *Rsp // 業務邏輯函數調用 if serviceFunc != nil { rsp, err = serviceFunc(c, req) } else if serviceOptFunc != nil { rsp, err = serviceOptFunc(c, req, opts...) } else { panic("must set ServiceFunc or ServiceFuncOpt") } // 處理響應 ProcessRsp(c, rsp, err) } }
這樣,現在一個接口基本上只需要一行代碼即可實現(不包括具體業務邏輯函數):
// 簡單請求,不需要認證 e.GET("/user/info/get", ginrest.Do(nil, GetUserInfo)) // 認證,綁定UID,處理 reqFunc := func(c *gin.Context, req *UpdateUserInfoReq) { req.UID = GetUID(c) } // 這里拆多一步是為了顯示第一個參數是ReqFunc e.POST("/user/info/update", Verify, ginrest.Do(reqFunc, UpdateUserInfo))
代碼地址,實現了一個基于gin的RESTful風格模板。
Golang標準庫自帶了一個線程安全、高性能、還能夠根據對象熱度自動進行釋放的對象池sync.Pool
,然而作為對象池,我們一般只會往里面放一種類型的對象,但sync.Pool
里面的元素還是interface{}
類型,因此我們可以簡單的封裝sync.Pool
,讓它里面的元素有具體類型:
這里其實就是簡單的對象sync.Pool
進行包裝,然后添加了一個ClearFunc()
在回收對象的時候進行一些清理操作,比如說byte切片
我們需要讓它的已用長度歸零(容量還是不變)。
// 創建新對象 type NewFunc[T any] func() T // 清理對象 type ClearFunc[T any] func(T) T type Pool[T any] struct { p sync.Pool clearFunc ClearFunc[T] } func New[T any](newFunc NewFunc[T], clearFunc ClearFunc[T]) *Pool[T] { if newFunc == nil { panic("must be provide NewFunc") } p := &Pool[T]{ clearFunc: clearFunc, } p.p.New = func() any { return newFunc() } return p } // 獲取對象 func (p *Pool[T]) Get() T { return p.p.Get().(T) } // 歸還對象 func (p *Pool[T]) Put(t T) { if p.clearFunc != nil { t = p.clearFunc(t) } p.p.Put(t) }
作為字節數組對象池使用:
newFunc := func() []byte { return make([]byte, size, cap) } clearFunc := func(b []byte) []byte { return b[:0] } p := New(newFunc, clearFunc) bytes := p.Get() // 這里bytes類型是[]byte p.Put(bytes)
代碼地址
對于緩存也是同理,目前大部分緩存庫的實現都是基于interface{}
或者是byte[]
,但是我們還是更加喜歡直接操作具體類型,因此我們可以自己使用泛型實現(或改造)一個緩存庫。我自己也實現了一個泛型緩存策略庫,里面包含LRU、LFU、ARC、NearlyLRU、TinyLFU等緩存策略。
讀到這里,這篇“怎么利用Golang泛型提高編碼效率”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。