編程模式之 Go 如何實現裝飾器
前言
哈嘍,大家好,我是
asong
。今天想與大家聊一聊如何用Go
實現裝飾器代碼。爲什麼會有這個想法呢?最近由於項目需要一直在看python
的代碼,在這個項目中應用了大量的裝飾器代碼,一個裝飾器代碼可以在全文共用,減少了冗餘代碼。python
的語法糖讓實現裝飾器變得很簡單,但是Go
語言的糖
不多,而且又是強類型的靜態無虛擬機的語言,所以,沒有辦法做到像Java
和Python
那樣寫出優雅的裝飾器的代碼,但也是可以實現的,今天我們就看看如何Go
語言寫出裝飾器代碼!
什麼是裝飾器
介紹裝飾器基本概念之前,我們先舉個例子,跟裝飾器很貼切:
如今我們的生活水平提高了,基本人手一臺手機,大家也知道手機屏幕摔到地板上是很容易碎屏的,手機屏幕一壞,又要多花一筆費用進行維修,很是心痛;那麼有什麼什麼辦法來避免這個問題呢,在不破壞手機屏幕結構的情況下,讓我們的手機更耐壞呢?其實我們只需要花幾元錢買一個鋼化膜,鋼化膜在不改變原有手機屏幕的結構下,讓手機變得更耐摔了。
根據上面這個例子,就可以引出本文的核心 -> 裝飾器。裝飾器本質就是:
函數裝飾器用於在源碼中 “標記” 函數,以某種方式增強函數的行爲。
裝飾器是一個強大的功能,但是若想掌握,必須要理解閉包!閉包的概念我們在下面一小節說明,我們先來看一看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)
這裏要實現功能很簡單,metric
就是一個裝飾器函數,他可以作用於任何函數之上,並打印該函數的執行時間,有個這個裝飾器,我們想要知道任何一個函數的執行時間,就簡便很多了。
簡單總結一下裝飾器使用場景:
-
插入日誌:使面向切面編程變的更簡單了。
-
緩存:讀寫緩存使用裝飾器來實現,減少了冗餘代碼。
-
事務處理:使代碼看起來更簡潔了。
-
權限校驗:權限校驗器是都是一套代碼,減少了冗餘代碼。
裝飾器的使用場景還用很多,就不一一列舉了,下面我們就來看看如何使用Go
也來實現裝飾器代碼吧!
閉包
裝飾器的實現和閉包是分不開的,所以我們先來學習一下什麼是閉包!
我們通常會把閉包和匿名函數弄混,這是因爲:在 函數內部定義函數不常見,直到開始使用匿名函數纔會這樣做。而且, 只有涉及嵌套函數時纔有閉包問題。因此,很多人是同時知道這兩個概念的。
其實,閉包指延伸了作用域的函數,其中包含函數定義體中引用、但是不在定義體中定義的非全局變量。函數是不是匿名的沒有關係,關鍵是 它能訪問定義體之外定義的非全局變量。
光看概念其實挺難理解閉包,我們通過例子來進行理解。
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))
}
這個例子,你猜運行結果是什麼?10,30
還是10,20
?
運行一下,答案出來了:10,20
。爲什麼會這樣呢?我們來分析一下!
上面的代碼中makeAverager
的寫法在C
語言中是不允許的,因爲在C
語言中,函數內的內存分配是在棧上的,在makeAverager
返回後,這部分棧就被回收了,但是在Go
語言中是沒有問題的,因爲Go
語言會進行escape analyze
分析出變量的作用範圍,將變量在堆上進行內存分配,我們使用go build --gcflags=-m ./test/test1.go
來看一下分析結果:
# 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
從運行結果我們可以看出,series
、func
、avg
都逃逸到了堆上。所以我們可以得出結論,series
變量和func(val float32) float32{}
被引用後,他所在的函數結束,也不會馬上銷燬,這也是變相延長了函數的生命週期!
小結:綜上所訴,閉包是一種函數,它會保留定義函數時存在的自由變量的綁定, 這樣調用函數時,雖然定義作用域不可用了,但是仍能使用那些綁定。
注意,只有嵌套在其他函數中的函數纔可能需要處理不在全局作用域中 的外部變量。
Gin 中裝飾器的應用
大家應該都使用過Gin
這個Web
框架,其在註冊路由時提供了中間件的使用,可以攔截 http 請求 - 響應生命週期的特殊函數,在請求 - 響應生命週期中可以註冊多箇中間件,每個中間件執行不同的功能,一箇中間執行完再輪到下一個中間件執行。這個中間件其實就是使用的裝飾器,我們來看一件簡單的例子:
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
函數,在註冊路由的時候添加進去就可以了,當有請求進來時,會先執行gin.HanderFunc
函數,在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
框架中的應用,這種設計大大減少了冗餘代碼的出現,也使代碼的可擴展性提高了。那麼接下來我們就在標準庫http
包上自己實現一個裝飾器,練習一下。
我們知道Go
語言的http
標準庫是不能使用中間件的,所以我們的機會來了,我們來給他實現一個!看代碼:
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)
}
}
實現起來還是比較簡單,這裏重新聲明瞭DecoratorHandler
類型,本質就是func(http.HandlerFunc) http.HandlerFunc
,這樣更加方便我們添加中間件函數,中間件按照添加的順序執行。
總結
好啦,本文到這裏就結束了,這一文我們學習了閉包的概念,通過閉包我們學習瞭如何在Go
語言中使用裝飾器,因爲Go
語言中不支持註解這個語法糖,所以使用裝飾器還是有點醜陋的,不過這個思想還是挺重要的,我們日常開發中可以參考這種思想,寫出更優質的代碼來!
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/B_VYr3I525-vjHgzfW3Jhg