Go語言常見設(shè)計(jì)模式之裝飾模式詳解
熟悉 Python 的同學(xué)想必對裝飾模式都不會(huì)太陌生,Python 從語法上原生支持裝飾器,大大提高了裝飾模式在 Python 中的應(yīng)用。而在 Go 語言中,雖然裝飾模式?jīng)]有像 Python 中應(yīng)用那么廣泛,但也有其用武之地,這篇文章我們就來一起看下裝飾模式在 Go 語言中的應(yīng)用。
簡單裝飾器
首先我們編寫一個(gè)簡單的 hello
函數(shù):
package main import "fmt" func hello() { fmt.Println("Hello World!") } func main() { hello() }
上面代碼執(zhí)行后輸出 Hello World!
。
現(xiàn)在我們想在打印 Hello World!
前后各加一行日志,最直接的實(shí)現(xiàn)方式如下:
package main import "fmt" func hello() { fmt.Println("before") fmt.Println("Hello World!") fmt.Println("after") } func main() { hello() }
代碼執(zhí)行后輸出:
before
Hello World!
after
更好的實(shí)現(xiàn)方式是單獨(dú)編寫一個(gè) logger
函數(shù),專門用來打印日志:
package main import "fmt" func logger(f func()) func() { return func() { fmt.Println("before") f() fmt.Println("after") } } func hello() { fmt.Println("Hello World!") } func main() { hello := logger(hello) hello() }
logger
函數(shù)接收一個(gè)函數(shù),并且返回一個(gè)函數(shù),而且參數(shù)和返回值的函數(shù)簽名同 hello
函數(shù)一樣。我們將原來調(diào)用 hello()
的地方改成:
hello := logger(hello) hello()
就實(shí)現(xiàn)了 logger
函數(shù)對 hello
函數(shù)的包裝,執(zhí)行后的打印結(jié)果仍為:
before
Hello World!
after
這樣我們就以一種更加優(yōu)雅的方式,實(shí)現(xiàn)了給 hello
函數(shù)增加日志的功能,因?yàn)檫@個(gè) logger
函數(shù)不僅可以用于 hello
,還可以用于其他任何與 hello
函數(shù)有著同樣簽名的函數(shù)。
logger
函數(shù)也就是我們在 Python 中經(jīng)常使用的裝飾器,如果按照 Python 中裝飾器的寫法,我們可以這樣做:
package main import "fmt" func logger(f func()) func() { return func() { fmt.Println("before") f() fmt.Println("after") } } // 給 hello 函數(shù)打上 logger 裝飾器 @logger func hello() { fmt.Println("Hello World!") } func main() { // hello 函數(shù)調(diào)用方式不變 hello() }
但很遺憾,上面的程序無法通過編譯,Go 語言目前還沒有像 Python 語言一樣從語法層面提供對裝飾器語法糖的支持。
裝飾器實(shí)現(xiàn)中間件
盡管 Go 語言中裝飾器的寫法不如 Python 語言精簡,但有一個(gè)場景的確非常適用,那就是 Web 開發(fā)場景中的中間件組件。
如果你用過 Gin
Web 框架,那么應(yīng)該很容易能看懂如下代碼:
package main import "github.com/gin-gonic/gin" func main() { r := gin.New() // 使用中間件 r.Use(gin.Logger(), gin.Recovery()) r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) _ = r.Run(":8888") }
在 Gin
框架中可以通過 r.Use(middlewares...)
的方式給路由增加非常多的中間件,這樣我們就能夠很方便的攔截路由處理函數(shù),并在其前后分別做一些處理邏輯。如上面示例中使用 gin.Logger()
增加日志,使用 gin.Recovery()
來處理 panic
異常。
Gin
框架的中間件正是使用裝飾模式來實(shí)現(xiàn)的,我們可以借用 Go 語言自帶的 http
庫來簡單模擬下:
package main import ( "fmt" "net/http" ) func loggerMiddleware(f http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { fmt.Println("before") f(w, r) fmt.Println("after") } } func authMiddleware(f http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if token := r.Header.Get("token"); token != "fake_token" { _, _ = w.Write([]byte("unauthorized\n")) return } f(w, r) } } func handleHello(w http.ResponseWriter, r *http.Request) { fmt.Println("handle hello") _, _ = w.Write([]byte("Hello World!\n")) } func main() { http.HandleFunc("/hello", authMiddleware(loggerMiddleware(handleHello))) fmt.Println(http.ListenAndServe(":8888", nil)) }
這是一個(gè)簡單的 Web Server 程序,其監(jiān)聽 8888
端口,當(dāng)訪問 /hello
路由時(shí)會(huì)進(jìn)入 handleHello
函數(shù)邏輯。
我們分別使用 loggerMiddleware
、authMiddleware
函數(shù)對 handleHello
進(jìn)行了包裝,使其支持打印訪問日志和認(rèn)證校驗(yàn)功能。假如我們還有其他中間件攔截功能需要加入,就可以這么無限包裝下去。
啟動(dòng)這個(gè) Server 來驗(yàn)證下裝飾器:
可以對結(jié)果進(jìn)行簡單分析,第一次請求 /hello
接口時(shí),由于沒有攜帶認(rèn)證 token
,收到了 unauthorized
響應(yīng);第二次請求時(shí)攜帶了 token
,得到響應(yīng) Hello World!
,并且后臺程序打印如下日志:
before
handle hello
after
說明中間件執(zhí)行順序是先由外向內(nèi)進(jìn)入,再由內(nèi)向外返回。而這種一層一層包裝處理邏輯的模型有一個(gè)非常形象的名字,叫作洋蔥模型。
這個(gè)名字可以說非常貼切了。但我們用洋蔥模型實(shí)現(xiàn)的中間件,相比于 Gin
框架的中間件寫法上還差點(diǎn)意思,這種一層層包裹函數(shù)的寫法不如 Gin
框架提供的 r.Use(middlewares...)
寫法直觀。
如果你去看 Gin
框架源碼,就會(huì)發(fā)現(xiàn)它的中間件和 handler
處理函數(shù)實(shí)際上會(huì)被一起聚合到路由節(jié)點(diǎn)的 handlers
屬性中,而這個(gè) handlers
屬性其實(shí)就是一個(gè) HandlerFunc
類型切片。對應(yīng)到咱們用 http
標(biāo)準(zhǔn)庫實(shí)現(xiàn)的 Web Server 中,就是滿足 func(ResponseWriter, *Request)
類型的 handler
切片。當(dāng)路由接口被調(diào)用時(shí),Gin
框架就會(huì)依次執(zhí)行 handlers
切片中的所有函數(shù),整個(gè)中間件的調(diào)用流程就像一條流水線一樣依次調(diào)用,再依次返回。而這種思想也有一個(gè)形象的名字,叫作流水線(Pipeline)。
接下來我們要做的就是將 handleHello
和兩個(gè)中間件 loggerMiddleware
、authMiddleware
聚合到一起,同樣形成一個(gè) Pipeline。
package main import ( "fmt" "net/http" ) func authMiddleware(f http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if token := r.Header.Get("token"); token != "fake_token" { _, _ = w.Write([]byte("unauthorized\n")) return } f(w, r) } } func loggerMiddleware(f http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { fmt.Println("before") f(w, r) fmt.Println("after") } } type handler func(http.HandlerFunc) http.HandlerFunc // 聚合 handler 和 middleware func pipelineHandlers(h http.HandlerFunc, hs ...handler) http.HandlerFunc { for i := range hs { h = hs[i](h) } return h } func handleHello(w http.ResponseWriter, r *http.Request) { fmt.Println("handle hello") _, _ = w.Write([]byte("Hello World!\n")) } func main() { http.HandleFunc("/hello", pipelineHandlers(handleHello, loggerMiddleware, authMiddleware)) fmt.Println(http.ListenAndServe(":8888", nil)) }
我們借用 pipelineHandlers
函數(shù)將 handler
和 middleware
聚合到一起,實(shí)現(xiàn)了讓這個(gè)簡單的 Web Server 中間件用法跟 Gin
框架用法相似的效果。
再次啟動(dòng) Server 進(jìn)行驗(yàn)證:
改造成功,跟之前使用洋蔥模型寫法的結(jié)果如出一轍。
總結(jié)
在簡單介紹了 Go 語言中如何實(shí)現(xiàn)裝飾模式后,我們通過對一個(gè) Web Server 程序中間件的講解,學(xué)習(xí)了裝飾模式在 Go 語言中的應(yīng)用。因?yàn)?Go 語言是靜態(tài)類型語言,不像 Python 那般靈活,所以在實(shí)現(xiàn)上要多費(fèi)一點(diǎn)力氣,并且 Go 語言實(shí)現(xiàn)的裝飾器由于有類型上的限制,也不如 Python 裝飾器那般通用,但仍有用武之地。
盡管我們最終實(shí)現(xiàn)的 pipelineHandlers
不如 Gin
框架中間件強(qiáng)大,比如不能延遲調(diào)用,通過 c.Next()
控制中間件調(diào)用流等,但通過這個(gè)簡單的示例,相信對你接下來深入學(xué)習(xí) Gin
框架會(huì)有所幫助。
以上就是Go語言常見設(shè)計(jì)模式之裝飾模式詳解的詳細(xì)內(nèi)容,更多關(guān)于Go裝飾模式的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
golang實(shí)現(xiàn)文件上傳并轉(zhuǎn)存數(shù)據(jù)庫功能
這篇文章主要為大家詳細(xì)介紹了golang實(shí)現(xiàn)文件上傳并轉(zhuǎn)存數(shù)據(jù)庫功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07GO利用channel協(xié)調(diào)協(xié)程的實(shí)現(xiàn)
本文主要介紹了GO利用channel協(xié)調(diào)協(xié)程的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05一文帶你使用golang手?jǐn)]一個(gè)websocket中間件
這篇文章主要為大家詳細(xì)介紹了如何使用golang手?jǐn)]一個(gè)websocket中間件,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的小伙伴可以參考一下2023-12-12Mac下Vs code配置Go語言環(huán)境的詳細(xì)過程
這篇文章給大家介紹Mac下Vs code配置Go語言環(huán)境的詳細(xì)過程,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-07-07