我们通过一个简单的例子来看一下装饰器的简单应用,首先编写一个 hello 函数:
简单装饰器
我们通过一个简单的例子来看一下装饰器的简单应用,首先编写一个 hello 函数:
1 | |
完成上面代码后,执行会输出“Hello World!”。接下来通过以下方式,在打印“Hello World!”前后各加一行日志:
1 | |
}
}
代码执行后输出:
1 | |
当然我们可以选择一个更好的实现方式,即单独编写一个专门用来打印日志的 logger 函数,示例如下:
func logger(f func()) func() {
return func() {
f()
}
}
hello := logger(hello)
}
1 | |
这样我们通过 logger 函数对 hello 函数的包装,更加优雅的实现了给 hello 函数增加日志的功能。执行后的打印结果仍为:
1 | |
其实 logger 函数也就是我们在 Python 中经常使用的装饰器,因为 logger 函数不仅可以用于 hello,还可以用于其他任何与 hello 函数有着同样签名的函数。
当然如果想使用 Python 中装饰器的写法,我们可以这样做:
f()
}
// 给 hello 函数打上 logger 装饰器
@logger
}
// hello 函数调用方式不变
}
1 | |
import “github.com/gin-gonic/gin”
r := gin.New()
// 使用中间件
r.Use(gin.Logger(), gin.Recovery())
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
1 | |
_ = r.Run(":8888")
1 | |
如示例中使用 gin.Logger() 增加日志,使用 gin.Recovery() 来处理 panic 异常一样,在 Gin 框架中可以通过 r.Use(middlewares…) 的方式给路由增加非常多的中间件,来方便我们拦截路由处理函数,并在其前后分别做一些处理逻辑。
而 Gin 框架的中间件正是使用装饰模式来实现的。下面我们借用 Go 语言自带的 http 库进行一个简单模拟。这是一个简单的 Web Server 程序,其监听 8888 端口,当访问 /hello 路由时会进入 handleHello 函数逻辑:
import (
“fmt”
“net/http”
)
func loggerMiddleware(f http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
f(w, r)
}
func authMiddleware(f http.HandlerFunc) http.HandlerFunc {
if token := r.Header.Get(“token”); token != “fake_token” {
_, _ = w.Write([]byte(“unauthorized\n”))
return
}
}
func handleHello(w http.ResponseWriter, r *http.Request) {
fmt.Println(“handle hello”)
_, _ = w.Write([]byte(“Hello World!\n”))
}
http.HandleFunc("/hello", authMiddleware(loggerMiddleware(handleHello)))
fmt.Println(http.ListenAndServe(":8888", nil))
}
1 | |
这说明中间件执行顺序是先由外向内进入,再由内向外返回。而这种一层一层包装处理逻辑的模型有一个非常形象且贴切的名字,洋葱模型。
但用洋葱模型实现的中间件有一个直观的问题。相比于 Gin 框架的中间件写法,这种一层层包裹函数的写法不如 Gin 框架提供的 r.Use(middlewares…) 写法直观。
Gin 框架源码的中间件和 handler 处理函数实际上被一起聚合到了路由节点的 handlers 属性中。其中 handlers 属性是 HandlerFunc 类型切片。对应到用 http 标准库实现的 Web Server 中,就是满足 func(ResponseWriter, *Request) 类型的 handler 切片。
当路由接口被调用时,Gin 框架就会像流水线一样依次调用执行 handlers 切片中的所有函数,再依次返回。这种思想也有一个形象的名字,就叫作流水线(Pipeline)。
接下来我们要做的就是将 handleHello 和两个中间件 loggerMiddleware、authMiddleware 聚合到一起,同样形成一个 Pipeline。
"fmt"
)
}
}
}
type handler func(http.HandlerFunc) http.HandlerFunc
// 聚合 handler 和 middleware
func pipelineHandlers(h http.HandlerFunc, hs …handler) http.HandlerFunc {
for i := range hs {
h = hsi
}
return h
}
}
http.HandleFunc("/hello", pipelineHandlers(handleHello, loggerMiddleware, authMiddleware))
}
1 | |
需要注意的是,尽管 Go 语言实现的装饰器有类型上的限制,不如 Python 装饰器那般通用。就像我们最终实现的 pipelineHandlers 不如 Gin 框架中间件强大,比如不能延迟调用,通过 c.Next() 控制中间件调用流等。但不能因为这样就放弃,因为 GO 语言装饰器依然有它的用武之地。
1 | |
本文标题: Golang常见设计模式之装饰器
发布时间: 2019年02月25日 00:00
最后更新: 2026年09月16日 05:40
原始链接: https://haoxiang.eu.org/d3f3ba9f/
版权声明: 本文著作权归作者所有,均采用CC BY-NC-SA 4.0许可协议,转载请注明出处!

