Golang 常見設計模式之裝飾模式
# Golang 常見設計模式之裝飾模式
想必只要是熟悉 Python 的同學對裝飾模式一定不會陌生,這類 Python 從語法上原生支援的裝飾器,大大提高了裝飾模式在 Python 中的應用。儘管 Go 語言中裝飾模式沒有 Python 中應用的那麼廣泛,但是它也有其獨到的地方。接下來就一起看下裝飾模式在 Go 語言中的應用。
### 簡單裝飾器
我們透過一個簡單的例子來看一下裝飾器的簡單應用,首先編寫一個 hello 函式:
```javascript
package main
import "fmt"
func hello() {
fmt.Println("Hello World!")
}
func main() {
hello()
}
```
完成上面程式碼後,執行會輸出 “Hello World!”。接下來透過以下方式,在列印 “Hello World!” 前後各加一行日誌:
```javascript
package main
import "fmt"
func hello() {
fmt.Println("before")
fmt.Println("Hello World!")
fmt.Println("after")
}
func main() {
hello()
}
```
程式碼執行後輸出:
```javascript
before
Hello World!
after
```
當然我們可以選擇一個更好的實現方式,即單獨編寫一個專門用來列印日誌的 logger 函式,示例如下:
```javascript
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 函式接收並返回了一個函式,且引數和返回值的函式簽名同 hello 一樣。然後我們在原來呼叫 hello () 的位置進行如下修改:
```javascript
hello := logger(hello)
hello()
```
這樣我們透過 logger 函式對 hello 函式的包裝,更加優雅的實現了給 hello 函式增加日誌的功能。執行後的列印結果仍為:
```javascript
before
Hello World!
after
```
其實 logger 函式也就是我們在 Python 中經常使用的裝飾器,因為 logger 函式不僅可以用於 hello,還可以用於其他任何與 hello 函式有著同樣簽名的函式。
當然如果想使用 Python 中裝飾器的寫法,我們可以這樣做:
```javascript
package main
import "fmt"
func logger(f func()) func() {
return func() {
fmt.Println("before")
f()
fmt.Println("after")
}
}
// 給 hello 函式打上 logger 裝飾器
@logger
func hello() {
fmt.Println("Hello World!")
}
func main() {
// hello 函式呼叫方式不變
hello()
}
```
但很遺憾,上面的程式無法透過編譯。因為 Go 語言目前還沒有像 Python 語言一樣從語法層面提供對裝飾器語法糖的支援。
## 裝飾器實現中介軟體
儘管 Go 語言中裝飾器的寫法不如 Python 語言精簡,但它被廣泛運用於 Web 開發場景的中介軟體元件中。比如 Gin Web 框架的如下程式碼,只要使用過就肯定會覺得熟悉:
```javascript
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.Logger () 增加日誌,使用 gin.Recovery () 來處理 panic 異常一樣,在 Gin 框架中可以透過 r.Use (middlewares...) 的方式給路由增加非常多的中介軟體,來方便我們攔截路由處理函式,並在其前後分別做一些處理邏輯。
而 Gin 框架的中介軟體正是使用裝飾模式來實現的。下面我們借用 Go 語言自帶的 http 庫進行一個簡單模擬。這是一個簡單的 Web Server 程式,其監聽 8888 埠,當訪問 /hello 路由時會進入 handleHello 函式邏輯:
```java
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))
}
```
我們分別使用 loggerMiddleware、authMiddleware 函式對 handleHello 進行了包裝,使其支援列印訪問日誌和認證校驗功能。如果我們還需要加入其他中介軟體攔截功能,可以透過這種方式進行無限包裝。
啟動這個 Server 來驗證下裝飾器:
![img]()
![img]()
對結果進行簡單分析可以看到,第一次請求 /hello 介面時,由於沒有攜帶認證 token,收到了 unauthorized 響應。第二次請求時攜帶了 token,則得到響應 “Hello World!”,並且後臺程式列印如下日誌:
```javascript
before
handle hello
after
```
這說明中介軟體執行順序是先由外向內進入,再由內向外返回。而這種一層一層包裝處理邏輯的模型有一個非常形象且貼切的名字,洋蔥模型。
![img]()
但用洋蔥模型實現的中介軟體有一個直觀的問題。相比於 Gin 框架的中介軟體寫法,這種一層層包裹函式的寫法不如 Gin 框架提供的 r.Use (middlewares...) 寫法直觀。
Gin 框架原始碼的中介軟體和 handler 處理函式實際上被一起聚合到了路由節點的 handlers 屬性中。其中 handlers 屬性是 HandlerFunc 型別切片。對應到用 http 標準庫實現的 Web Server 中,就是滿足 func (ResponseWriter, *Request) 型別的 handler 切片。
當路由介面被呼叫時,Gin 框架就會像流水線一樣依次呼叫執行 handlers 切片中的所有函式,再依次返回。這種思想也有一個形象的名字,就叫作流水線(Pipeline)。
![img]()
接下來我們要做的就是將 handleHello 和兩個中介軟體 loggerMiddleware、authMiddleware 聚合到一起,同樣形成一個 Pipeline。
```javascript
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 函式將 handler 和 middleware 聚合到一起,實現了讓這個簡單的 Web Server 中介軟體用法跟 Gin 框架用法相似的效果。
再次啟動 Server 進行驗證:
![img]()
![img]()
改造成功,跟之前使用洋蔥模型寫法的結果如出一轍。
## 總結
簡單瞭解了 Go 語言中如何實現裝飾模式後,我們透過一個 Web Server 程式中介軟體,學習了裝飾模式在 Go 語言中的應用。
需要注意的是,儘管 Go 語言實現的裝飾器有型別上的限制,不如 Python 裝飾器那般通用。就像我們最終實現的 pipelineHandlers 不如 Gin 框架中介軟體強大,比如不能延遲呼叫,透過 c.Next () 控制中介軟體呼叫流等。但不能因為這樣就放棄,因為 GO 語言裝飾器依然有它的用武之地。Go 語言是靜態型別語言不像 Python 那般靈活,所以在實現上要多費一點力氣。感興趣的朋友可以在3A的雲伺服器上部署一套環境進行嘗試練習希望透過這個簡單的示例,相信對大家深入學習 Gin 框架有所幫助。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70021806/viewspace-2913985/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Golang 常見設計模式之選項模式Golang設計模式
- Golang 常見設計模式之單例模式Golang設計模式單例
- GoLang設計模式21 - 裝飾模式Golang設計模式
- 【設計模式之裝飾模式】設計模式
- 設計模式之裝飾模式設計模式
- 設計模式系列之「裝飾模式」設計模式
- 設計模式之-裝飾器模式設計模式
- 設計模式之【裝飾器模式】設計模式
- 設計模式之裝飾者模式設計模式
- 設計模式之裝飾者模式(二)設計模式
- Go 設計模式之裝飾器模式Go設計模式
- 設計模式之裝飾者模式(一)設計模式
- PHP設計模式之裝飾者模式PHP設計模式
- Java設計模式之裝飾者模式Java設計模式
- java設計模式之裝飾器模式Java設計模式
- 我學設計模式 之裝飾模式設計模式
- 設計模式——裝飾模式設計模式
- 設計模式-裝飾模式設計模式
- 常見的Golang設計模式實現?Golang設計模式
- 設計模式-裝飾設計模式設計模式
- 設計模式之裝飾器模式(decorator pattern)設計模式
- 【趣味設計模式系列】之【裝飾器模式】設計模式
- PHP設計模式之裝飾器模式(Decorator)PHP設計模式
- android常用設計模式之裝飾模式Android設計模式
- JAVA設計模式之 裝飾模式【Decorator Pattern】Java設計模式
- Java設計模式之裝飾模式趣談Java設計模式
- Java學設計模式之裝飾器模式Java設計模式
- 設計模式----裝飾器模式設計模式
- 設計模式-裝飾者模式設計模式
- 設計模式——裝飾者模式設計模式
- 設計模式-裝飾器模式設計模式
- [設計模式] 裝飾器模式設計模式
- 設計模式(八):裝飾模式設計模式
- [設計模式]裝飾者模式設計模式
- 8.java設計模式之裝飾者模式Java設計模式
- Java設計模式之裝飾者模式(Decorator pattern)Java設計模式
- 設計模式(十一)----結構型模式之裝飾者模式設計模式
- 裝飾設計模式設計模式