自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

編程模式之Go如何實現(xiàn)裝飾器

開發(fā) 后端
今天想與大家聊一聊如何用Go實現(xiàn)裝飾器代碼。為什么會有這個想法呢?最近由于項目需要一直在看python的代碼,在這個項目中應(yīng)用了大量的裝飾器代碼,一個裝飾器代碼可以在全文共用,減少了冗余代碼。

[[410713]]

本文轉(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是如何使用裝飾器的:

  1. def metric(fn): 
  2.     @functools.wraps(fn) 
  3.     def timer(*arag, **kw): 
  4.         start = time.time() 
  5.         num = fn(*arag, **kw) 
  6.         end = time.time() 
  7.         times = (end - start) * 1000 
  8.         print('%s executed in %s ms' % (fn.__name__, times)) 
  9.         return num 
  10.     return timer 
  11.  
  12. @metric 
  13. def Sum(x, y): 
  14.     time.sleep(0.0012) 
  15.     return x + y; 
  16.  
  17.  
  18. 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)行理解。

  1. func makeAverager() func(val float32) float32{ 
  2.  series := make([]float32,0) 
  3.  return func(val float32) float32 { 
  4.   series = append(series, val) 
  5.   total := float32(0) 
  6.   for _,v:=range series{ 
  7.    total +=v 
  8.   } 
  9.   return total/ float32(len(series)) 
  10.  } 
  11.  
  12. func main() { 
  13.  avg := makeAverager() 
  14.  fmt.Println(avg(10)) 
  15.  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é)果:

  1. # command-line-arguments 
  2. test/test1.go:21:13: inlining call to fmt.Println 
  3. test/test1.go:22:13: inlining call to fmt.Println 
  4. test/test1.go:8:2: moved to heap: series 
  5. test/test1.go:8:16: make([]float32, 0) escapes to heap 
  6. test/test1.go:9:9: func literal escapes to heap 
  7. test/test1.go:21:17: avg(10) escapes to heap 
  8. test/test1.go:21:13: []interface {} literal does not escape 
  9. test/test1.go:22:17: avg(30) escapes to heap 
  10. test/test1.go:22:13: []interface {} literal does not escape 
  11. <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í)行。這個中間件其實就是使用的裝飾器,我們來看一件簡單的例子:

  1. func VerifyHeader() gin.HandlerFunc { 
  2.  return func(c *gin.Context) { 
  3.   header := c.Request.Header.Get("token"
  4.   if header == "" { 
  5.    c.JSON(200, gin.H{ 
  6.     "code":   1000, 
  7.     "msg":    "Not logged in"
  8.    }) 
  9.    return 
  10.   } 
  11.  } 
  12. func main()  { 
  13.  r := gin.Default() 
  14.  group := r.Group("/api/asong",VerifyHeader()) 
  15.  { 
  16.   group.GET("/ping", func(context *gin.Context) { 
  17.    context.JSON(200,gin.H{ 
  18.     "message""pong"
  19.    }) 
  20.   }) 
  21.  } 
  22.  r.Run() 

這段代碼很簡單,我們只需要寫一個VerifyHeader函數(shù),在注冊路由的時候添加進(jìn)去就可以了,當(dāng)有請求進(jìn)來時,會先執(zhí)行g(shù)in.HanderFunc函數(shù),在Gin框架中使用一個切片來存儲的,所以在添加中間件時,要注意添加順序哦!

  1. // HandlerFunc defines the handler used by gin middleware as return value. 
  2. type HandlerFunc func(*Context) 
  3.  
  4. // HandlersChain defines a HandlerFunc array. 
  5. type HandlersChain []HandlerFunc 
  6.  
  7. func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain { 
  8.  finalSize := len(group.Handlers) + len(handlers) 
  9.  if finalSize >= int(abortIndex) { 
  10.   panic("too many handlers"
  11.  } 
  12.  mergedHandlers := make(HandlersChain, finalSize) 
  13.  copy(mergedHandlers, group.Handlers) 
  14.  copy(mergedHandlers[len(group.Handlers):], handlers) 
  15.  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)一個!看代碼:

  1. type DecoratorHandler func(http.HandlerFunc) http.HandlerFunc 
  2.  
  3. func MiddlewareHandlerFunc(hp http.HandlerFunc, decors ...DecoratorHandler) http.HandlerFunc { 
  4.  for d := range decors { 
  5.   dp := decors[len(decors)-1-d] 
  6.   hp = dp(hp) 
  7.  } 
  8.  return hp 
  9.  
  10. func VerifyHeader(h http.HandlerFunc) http.HandlerFunc { 
  11.  return func(w http.ResponseWriter, r *http.Request) { 
  12.   token := r.Header.Get("token"
  13.   if token == "" { 
  14.    fmt.Fprintf(w,r.URL.Path +" response: Not Logged in"
  15.    return 
  16.   } 
  17.   h(w,r) 
  18.  } 
  19.  
  20. func Pong(w http.ResponseWriter, r *http.Request)  { 
  21.  fmt.Fprintf(w,r.URL.Path +"response: pong"
  22.  return 
  23.  
  24.  
  25. func main()  { 
  26.  http.HandleFunc("/api/asong/ping",MiddlewareHandlerFunc(Pong,VerifyHeader)) 
  27.  err := http.ListenAndServe(":8080", nil) 
  28.  if err != nil { 
  29.   log.Fatal("ListenAndServe: ", err) 
  30.  } 

實現(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ì)的代碼來!

 

責(zé)任編輯:武曉燕 來源: Golang夢工廠
相關(guān)推薦

2023-09-04 13:14:00

裝飾器設(shè)計模式

2023-12-13 13:28:16

裝飾器模式Python設(shè)計模式

2024-04-10 12:27:43

Python設(shè)計模式開發(fā)

2023-01-09 08:43:53

Go設(shè)計模式

2022-09-14 08:16:48

裝飾器模式對象

2020-12-01 07:16:05

重學(xué)設(shè)計模式

2022-03-25 11:01:28

Golang裝飾模式Go 語言

2022-10-24 07:31:53

Python編程裝飾器

2021-11-28 22:33:01

Go選項模式

2023-05-15 08:51:46

解釋器模式定義

2022-01-19 08:21:12

設(shè)計裝飾器模式

2024-02-23 12:11:53

裝飾器模式對象

2010-02-01 17:50:32

Python裝飾器

2022-04-24 15:29:17

微服務(wù)go

2021-11-08 07:41:16

Go流水線編程

2021-06-03 09:18:25

裝飾器模式包裝

2022-09-19 23:04:08

Python裝飾器語言

2021-11-26 00:04:20

Go計時器重構(gòu)

2022-05-13 23:46:52

GO編程內(nèi)存

2023-09-28 15:43:03

裝飾者模式代理定義
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號