是Golang中常见的设计模式,熟悉Python的同学想必不会陌生。这种Python在语法上原生支持的装饰器,大大提高了装饰模式在Python中的应用。Go语言中的装饰模式虽然没有Python中广泛使用,但也有其独到之处。下面我们就来看看装饰模式在Go语言中的应用。简单装饰器下面通过一个简单的例子来看看装饰器的简单应用,先写一个hello函数:packagemainimport"fmt"funchello(){fmt.Println("HelloWorld!")}funcmain(){hello()}完成以上代码后,执行会输出“HelloWorld!”。接下来在打印“HelloWorld!”前后添加一行日志通过以下方式:}代码执行后输出为:beforeHelloWorld!after当然我们可以选择更好的实现方式,那就是写一个专门用来打印日志的logger函数。示例如下:packagemainimport"fmt"funclogger(ffunc())func(){returnfunc(){fmt.Println("before")f()fmt.Println("after")}}funchello(){fmt.Println("HelloWorld!")}funcmain(){hello:=logger(hello)hello()}可以看到logger函数接收并返回一个函数,函数签名参数和返回值同hello。然后我们在原来的hello()调用处做如下修改:hello:=logger(hello)hello()这样我们就用logger函数包装了hello函数,实现了给hello函数添加日志的功能更优雅。执行后的打印结果仍然是:beforeHelloWorld!after实际上,logger函数也是我们在Python中经常使用的装饰器,因为logger函数不仅可以用于hello,还可以用于任何其他具有相同签名的函数作为你好功能。当然,如果我们想在Python中使用装饰器,我们可以这样做:)fmt.Println("after")}}//给hello函数添加一个logger装饰器@loggerfunchello(){fmt.Println("HelloWorld!")}funcmain(){//调用方法hello函数保持不变hello()}但不幸的是,上面的程序无法通过编译。因为Go语言目前并没有像Python语言那样提供装饰器的语法糖支持。装饰器实现中间件虽然Go语言中的装饰器没有Python语言那么简洁,但是在Web开发场景中广泛应用于中间件组件。比如下面这段GinWeb框架的代码,只要你用过,肯定会觉得眼熟:packagemainimport"github.com/gin-gonic/gin"funcmain(){r:=gin.New()//使用中间件r.使用(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库来进行一个简单的模拟。这是一个简单的监听8888端口的WebServer程序,当访问/hello路由时,会进入handleHello函数逻辑:packagemainimport("fmt""net/http")funcloggerMiddleware(fhttp.HandlerFunc)http。HandlerFunc{returnfunc(whttp.ResponseWriter,r*http.Request){fmt.Println("before")f(w,r)fmt.Println("after")}}funcauthMiddleware(fhttp.HandlerFunc)http.HandlerFunc{returnfunc(whttp.ResponseWriter,r*http.Request){iftoken:=r.Header.Get("token");token!="fake_token"{_,_=w.Write([]byte("unauthorized\n"))返回}f(w,r)}}funchandleHello(whttp.ResponseWriter,r*http.Request){fmt.Println("handlehello")_,_=w.Write([]byte("HelloWorld!\n"))}funcmain(){http.HandleFunc("/hello",authMiddleware(loggerMiddleware(handleHello)))fmt.Println(http.ListenAndServe(":8888",nil))}我们使用loggerMiddleware和authMiddleware函数分别对handleHello进行包装,使其支持打印访问日志和认证验证功能。如果我们需要添加其他的中间件拦截功能,可以使用这种方式进行无限封装。启动本服务器验证装饰器:简单分析结果发现,第一次请求/hello接口时,由于没有携带认证token,收到未授权响应。如果第二次请求中携带了token,则返回“HelloWorld!”得到,后台程序打印如下日志:beforehandlehelloafter,说明中间件的执行顺序是先从外向内进入,再从内向外返回。而这种层层封装处理逻辑模型有一个非常形象贴切的名字,洋葱模型。但是用洋葱模型实现的中间件有一个直观的问题。与Gin框架的中间件写法相比,这种逐层包装函数的写法不如Gin框架提供的r.Use(middlewares...)写法直观。Gin框架源码的中间件和handler处理函数其实都聚合到了路由节点的handlers属性中。handlers属性是一个HandlerFunc类型的切片。对应用http标准库实现的WebServer,是一个满足func(ResponseWriter,*Request)类型的handlerslice。当路由接口被调用时,Gin框架会像流水线一样依次调用执行handlers切片中的所有函数,然后依次返回。这种思路还有一个形象的名字,叫做Pipeline。接下来我们要做的就是将handleHello和两个中间件loggerMiddleware和authMiddleware聚合在一起,形成一个Pipeline。packagemainimport("fmt""net/http")funcauthMiddleware(fhttp.HandlerFunc)http.HandlerFunc{returnfunc(whttp.ResponseWriter,r*http.Request){iftoken:=r.Header.Get("令牌");token!="fake_token"{_,_=w.Write([]byte("unauthorized\n"))return}f(w,r)}}funcloggerMiddleware(fhttp.HandlerFunc)http.HandlerFunc{返回func(whttp.ResponseWriter,r*http.Request){fmt.Println("before")f(w,r)fmt.Println("after")}}typehandlerfunc(http.HandlerFunc)http.HandlerFunc//聚合handler和middlewarefuncpipelineHandlers(hhttp.HandlerFunc,hs...handler)http.HandlerFunc{fori:=rangehs{h=hs[i](h)}returnh}funchandleHello(whttp.ResponseWriter,r*http.Request){fmt.Println("handlehello")_,_=w.Write([]byte("HelloWorld!\n"))}funcmain(){http.HandleFunc("/hello",pipelineHandlers(handleHello,loggerMiddleware,authMiddleware))fmt.Println(http.ListenAndServe(":8888",nil))}我们使用pipelineHandlers函数聚合处理程序和中间件into一起,这个简单的WebServer中间件的用法和Gin框架的用法类似。再次启动Server验证:改造成功,与之前使用洋葱模型写法得到的结果一模一样。小结在简单了解了Go语言中装饰模式的实现方式后,我们通过一个WebServer程序中间件学习了装饰模式在Go语言中的应用。需要注意的是,Go语言实现的装饰器虽然有类型限制,但并不像Python装饰器那么通用。就像我们最终实现的pipelineHandlers没有Gin框架的中间件那么强大,比如不能延迟调用,可以通过c.Next()来控制中间件的调用流程。但是你不能因此而放弃,因为GO语言的装饰器还是有一席之地的。Go语言是一种静态类型的语言,没有Python灵活,所以实现起来要费点功夫。我希望这个简单的例子能帮助你更多地了解Gin框架。提升硬盘存储数据写入效率的两种方法推荐阅读【程序员实用工具推荐】Mac效率神器Alfred
