編程模式之Go如何實現(xiàn)裝飾器
本文轉(zhuǎn)載自微信公眾號「Golang夢工廠」,作者AsongGo。轉(zhuǎn)載本文請聯(lián)系Golang夢工廠公眾號。
前言
哈嘍,大家好,我是asong。今天想與大家聊一聊如何用Go實現(xiàn)裝飾器代碼。為什么會有這個想法呢?最近由于項目需要一直在看python的代碼,在這個項目中應(yīng)用了大量的裝飾器代碼,一個裝飾器代碼可以在全文共用,減少了冗余代碼。python的語法糖讓實現(xiàn)裝飾器變得很簡單,但是Go語言的糖不多,而且又是強(qiáng)類型的靜態(tài)無虛擬機(jī)的語言,所以,沒有辦法做到像Java 和 Python 那樣寫出優(yōu)雅的裝飾器的代碼,但也是可以實現(xiàn)的,今天我們就看看如何Go語言寫出裝飾器代碼!
什么是裝飾器
介紹裝飾器基本概念之前,我們先舉個例子,跟裝飾器很貼切:
如今我們的生活水平提高了,基本人手一臺手機(jī),大家也知道手機(jī)屏幕摔到地板上是很容易碎屏的,手機(jī)屏幕一壞,又要多花一筆費(fèi)用進(jìn)行維修,很是心痛;那么有什么什么辦法來避免這個問題呢,在不破壞手機(jī)屏幕結(jié)構(gòu)的情況下,讓我們的手機(jī)更耐壞呢?其實我們只需要花幾元錢買一個鋼化膜,鋼化膜在不改變原有手機(jī)屏幕的結(jié)構(gòu)下,讓手機(jī)變得更耐摔了。
根據(jù)上面這個例子,就可以引出本文的核心 -> 裝飾器。裝飾器本質(zhì)就是:
函數(shù)裝飾器用于在源碼中“標(biāo)記”函數(shù),以某種方式增強(qiáng)函數(shù)的行為。
裝飾器是一個強(qiáng)大的功能,但是若想掌握,必須要理解閉包!閉包的概念我們在下面一小節(jié)說明,我們先來看一看python是如何使用裝飾器的:
- def metric(fn):
- @functools.wraps(fn)
- def timer(*arag, **kw):
- start = time.time()
- num = fn(*arag, **kw)
- end = time.time()
- times = (end - start) * 1000
- print('%s executed in %s ms' % (fn.__name__, times))
- return num
- return timer
- @metric
- def Sum(x, y):
- time.sleep(0.0012)
- return x + y;
- Sum(10, 20)
這里要實現(xiàn)功能很簡單,metric就是一個裝飾器函數(shù),他可以作用于任何函數(shù)之上,并打印該函數(shù)的執(zhí)行時間,有個這個裝飾器,我們想要知道任何一個函數(shù)的執(zhí)行時間,就簡便很多了。
簡單總結(jié)一下裝飾器使用場景:
- 插入日志:使面向切面編程變的更簡單了。
- 緩存:讀寫緩存使用裝飾器來實現(xiàn),減少了冗余代碼。
- 事務(wù)處理:使代碼看起來更簡潔了。
- 權(quán)限校驗:權(quán)限校驗器是都是一套代碼,減少了冗余代碼。
裝飾器的使用場景還用很多,就不一一列舉了,下面我們就來看看如何使用Go也來實現(xiàn)裝飾器代碼吧!
閉包
裝飾器的實現(xiàn)和閉包是分不開的,所以我們先來學(xué)習(xí)一下什么是閉包!
我們通常會把閉包和匿名函數(shù)弄混,這是因為:在 函數(shù)內(nèi)部定義函數(shù)不常見,直到開始使用匿名函數(shù)才會這樣做。而且, 只有涉及嵌套函數(shù)時才有閉包問題。因此,很多人是同時知道這兩個概念的。
其實,閉包指延伸了作用域的函數(shù),其中包含函數(shù)定義體中引用、但是不在定義體中定義的非全局變量。函數(shù)是不是匿名的沒有關(guān)系,關(guān)鍵是 它能訪問定義體之外定義的非全局變量。
光看概念其實挺難理解閉包,我們通過例子來進(jìn)行理解。
- func makeAverager() func(val float32) float32{
- series := make([]float32,0)
- return func(val float32) float32 {
- series = append(series, val)
- total := float32(0)
- for _,v:=range series{
- total +=v
- }
- return total/ float32(len(series))
- }
- }
- func main() {
- avg := makeAverager()
- fmt.Println(avg(10))
- fmt.Println(avg(30))
- }
這個例子,你猜運(yùn)行結(jié)果是什么?10,30還是10,20?
運(yùn)行一下,答案出來了:10,20。為什么會這樣呢?我們來分析一下!
上面的代碼中makeAverager的寫法在C語言中是不允許的,因為在C語言中,函數(shù)內(nèi)的內(nèi)存分配是在棧上的,在makeAverager返回后,這部分棧就被回收了,但是在Go語言中是沒有問題的,因為Go語言會進(jìn)行escape analyze分析出變量的作用范圍,將變量在堆上進(jìn)行內(nèi)存分配,我們使用go build --gcflags=-m ./test/test1.go來看一下分析結(jié)果:
- # command-line-arguments
- test/test1.go:21:13: inlining call to fmt.Println
- test/test1.go:22:13: inlining call to fmt.Println
- test/test1.go:8:2: moved to heap: series
- test/test1.go:8:16: make([]float32, 0) escapes to heap
- test/test1.go:9:9: func literal escapes to heap
- test/test1.go:21:17: avg(10) escapes to heap
- test/test1.go:21:13: []interface {} literal does not escape
- test/test1.go:22:17: avg(30) escapes to heap
- test/test1.go:22:13: []interface {} literal does not escape
- <autogenerated>:1: .this does not escape
從運(yùn)行結(jié)果我們可以看出,series、func、avg都逃逸到了堆上。所以我們可以得出結(jié)論,series變量和func(val float32) float32{}被引用后,他所在的函數(shù)結(jié)束,也不會馬上銷毀,這也是變相延長了函數(shù)的生命周期!
小結(jié):綜上所訴,閉包是一種函數(shù),它會保留定義函數(shù)時存在的自由變量的綁定, 這樣調(diào)用函數(shù)時,雖然定義作用域不可用了,但是仍能使用那些綁定。
注意,只有嵌套在其他函數(shù)中的函數(shù)才可能需要處理不在全局作用域中 的外部變量。
Gin中裝飾器的應(yīng)用
大家應(yīng)該都使用過Gin這個Web框架,其在注冊路由時提供了中間件的使用,可以攔截http請求-響應(yīng)生命周期的特殊函數(shù),在請求-響應(yīng)生命周期中可以注冊多個中間件,每個中間件執(zhí)行不同的功能,一個中間執(zhí)行完再輪到下一個中間件執(zhí)行。這個中間件其實就是使用的裝飾器,我們來看一件簡單的例子:
- func VerifyHeader() gin.HandlerFunc {
- return func(c *gin.Context) {
- header := c.Request.Header.Get("token")
- if header == "" {
- c.JSON(200, gin.H{
- "code": 1000,
- "msg": "Not logged in",
- })
- return
- }
- }
- }
- func main() {
- r := gin.Default()
- group := r.Group("/api/asong",VerifyHeader())
- {
- group.GET("/ping", func(context *gin.Context) {
- context.JSON(200,gin.H{
- "message": "pong",
- })
- })
- }
- r.Run()
- }
這段代碼很簡單,我們只需要寫一個VerifyHeader函數(shù),在注冊路由的時候添加進(jìn)去就可以了,當(dāng)有請求進(jìn)來時,會先執(zhí)行g(shù)in.HanderFunc函數(shù),在Gin框架中使用一個切片來存儲的,所以在添加中間件時,要注意添加順序哦!
- // HandlerFunc defines the handler used by gin middleware as return value.
- type HandlerFunc func(*Context)
- // HandlersChain defines a HandlerFunc array.
- type HandlersChain []HandlerFunc
- func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
- finalSize := len(group.Handlers) + len(handlers)
- if finalSize >= int(abortIndex) {
- panic("too many handlers")
- }
- mergedHandlers := make(HandlersChain, finalSize)
- copy(mergedHandlers, group.Handlers)
- copy(mergedHandlers[len(group.Handlers):], handlers)
- return mergedHandlers
- }
net/http使用裝飾器
上面我們看到了裝飾器在Gin框架中的應(yīng)用,這種設(shè)計大大減少了冗余代碼的出現(xiàn),也使代碼的可擴(kuò)展性提高了。那么接下來我們就在標(biāo)準(zhǔn)庫http包上自己實現(xiàn)一個裝飾器,練習(xí)一下。
我們知道Go語言的http標(biāo)準(zhǔn)庫是不能使用中間件的,所以我們的機(jī)會來了,我們來給他實現(xiàn)一個!看代碼:
- type DecoratorHandler func(http.HandlerFunc) http.HandlerFunc
- func MiddlewareHandlerFunc(hp http.HandlerFunc, decors ...DecoratorHandler) http.HandlerFunc {
- for d := range decors {
- dp := decors[len(decors)-1-d]
- hp = dp(hp)
- }
- return hp
- }
- func VerifyHeader(h http.HandlerFunc) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- token := r.Header.Get("token")
- if token == "" {
- fmt.Fprintf(w,r.URL.Path +" response: Not Logged in")
- return
- }
- h(w,r)
- }
- }
- func Pong(w http.ResponseWriter, r *http.Request) {
- fmt.Fprintf(w,r.URL.Path +"response: pong")
- return
- }
- func main() {
- http.HandleFunc("/api/asong/ping",MiddlewareHandlerFunc(Pong,VerifyHeader))
- err := http.ListenAndServe(":8080", nil)
- if err != nil {
- log.Fatal("ListenAndServe: ", err)
- }
- }
實現(xiàn)起來還是比較簡單,這里重新聲明了DecoratorHandler類型,本質(zhì)就是func(http.HandlerFunc) http.HandlerFunc,這樣更加方便我們添加中間件函數(shù),中間件按照添加的順序執(zhí)行。
總結(jié)
好啦,本文到這里就結(jié)束了,這一文我們學(xué)習(xí)了閉包的概念,通過閉包我們學(xué)習(xí)了如何在Go語言中使用裝飾器,因為Go語言中不支持注解這個語法糖,所以使用裝飾器還是有點(diǎn)丑陋的,不過這個思想還是挺重要的,我們?nèi)粘i_發(fā)中可以參考這種思想,寫出更優(yōu)質(zhì)的代碼來!