Golang 常见设计模式之装饰模式

Golang 常见设计模式之装饰模式

想必只要是熟悉 Python 的同学对装饰模式一定不会陌生,这类 Python 从语法上原生支持的装饰器,大大提高了装饰模式在 Python 中的应用。尽管 Go 语言中装饰模式没有 Python 中应用的那么广泛,但是它也有其独到的地方。接下来就一起看下装饰模式在 Go 语言中的应用。

简单装饰器

我们通过一个简单的例子来看一下装饰器的简单应用,首先编写一个 hello 函数:

1package main 2 3import "fmt" 4 5func hello() { 6 fmt.Println("Hello World!") 7} 8 9func main() { 10 hello() 11}

完成上面代码后,执行会输出 “Hello World!”。接下来通过以下方式,在打印 “Hello World!” 前后各加一行日志:

1package main 2 3import "fmt" 4 5func hello() { 6 fmt.Println("before") 7 fmt.Println("Hello World!") 8 fmt.Println("after") 9} 10 11func main() { 12 hello() 13}

代码执行后输出:

1before 2Hello World! 3after

当然我们可以选择一个更好的实现方式,即单独编写一个专门用来打印日志的 logger 函数,示例如下:

1package main 2 3import "fmt" 4 5func logger(f func()) func() { 6 return func() { 7 fmt.Println("before") 8 f() 9 fmt.Println("after") 10 } 11} 12 13func hello() { 14 fmt.Println("Hello World!") 15} 16 17func main() { 18 hello := logger(hello) 19 hello() 20}

可以看到 logger 函数接收并返回了一个函数,且参数和返回值的函数签名同 hello 一样。然后我们在原来调用 hello () 的位置进行如下修改:

1hello := logger(hello) 2hello()

这样我们通过 logger 函数对 hello 函数的包装,更加优雅的实现了给 hello 函数增加日志的功能。执行后的打印结果仍为:

1before 2Hello World! 3after

其实 logger 函数也就是我们在 Python 中经常使用的装饰器,因为 logger 函数不仅可以用于 hello,还可以用于其他任何与 hello 函数有着同样签名的函数。

当然如果想使用 Python 中装饰器的写法,我们可以这样做:

1package main 2 3import "fmt" 4 5func logger(f func()) func() { 6 return func() { 7 fmt.Println("before") 8 f() 9 fmt.Println("after") 10 } 11} 12 13// 给 hello 函数打上 logger 装饰器 14@logger 15func hello() { 16 fmt.Println("Hello World!") 17} 18 19func main() { 20 // hello 函数调用方式不变 21 hello() 22}

但很遗憾,上面的程序无法通过编译。因为 Go 语言目前还没有像 Python 语言一样从语法层面提供对装饰器语法糖的支持。

装饰器实现中间件

尽管 Go 语言中装饰器的写法不如 Python 语言精简,但它被广泛运用于 Web 开发场景的中间件组件中。比如 Gin Web 框架的如下代码,只要使用过就肯定会觉得熟悉:

1package main 2 3import "github.com/gin-gonic/gin" 4 5func main() { 6 r := gin.New() 7 8 // 使用中间件 9 r.Use(gin.Logger(), gin.Recovery()) 10 11 r.GET("/ping", func(c *gin.Context) { 12 c.JSON(200, gin.H{ 13 "message": "pong", 14 }) 15 }) 16 _ = r.Run(":8888") 17}

如示例中使用 gin.Logger () 增加日志,使用 gin.Recovery () 来处理 panic 异常一样,在 Gin 框架中可以通过 r.Use (middlewares...) 的方式给路由增加非常多的中间件,来方便我们拦截路由处理函数,并在其前后分别做一些处理逻辑。

而 Gin 框架的中间件正是使用装饰模式来实现的。下面我们借用 Go 语言自带的 http 库进行一个简单模拟。这是一个简单的 Web Server 程序,其监听 8888 端口,当访问 /hello 路由时会进入 handleHello 函数逻辑:

1package main 2 3import ( 4 "fmt" 5 "net/http" 6) 7 8func loggerMiddleware(f http.HandlerFunc) http.HandlerFunc { 9 return func(w http.ResponseWriter, r *http.Request) { 10 fmt.Println("before") 11 f(w, r) 12 fmt.Println("after") 13 } 14} 15 16func authMiddleware(f http.HandlerFunc) http.HandlerFunc { 17 return func(w http.ResponseWriter, r *http.Request) { 18 if token := r.Header.Get("token"); token != "fake_token" { 19 _, _ = w.Write([]byte("unauthorized\n")) 20 return 21 } 22 f(w, r) 23 } 24} 25 26func handleHello(w http.ResponseWriter, r *http.Request) { 27 fmt.Println("handle hello") 28 _, _ = w.Write([]byte("Hello World!\n")) 29} 30 31func main() { 32 http.HandleFunc("/hello", authMiddleware(loggerMiddleware(handleHello))) 33 fmt.Println(http.ListenAndServe(":8888", nil)) 34}

我们分别使用 loggerMiddleware、authMiddleware 函数对 handleHello 进行了包装,使其支持打印访问日志和认证校验功能。如果我们还需要加入其他中间件拦截功能,可以通过这种方式进行无限包装。

启动这个 Server 来验证下装饰器:

img

img

对结果进行简单分析可以看到,第一次请求 /hello 接口时,由于没有携带认证 token,收到了 unauthorized 响应。第二次请求时携带了 token,则得到响应 “Hello World!”,并且后台程序打印如下日志:

1before 2handle hello 3after

这说明中间件执行顺序是先由外向内进入,再由内向外返回。而这种一层一层包装处理逻辑的模型有一个非常形象且贴切的名字,洋葱模型。

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。

1package main 2 3import ( 4 "fmt" 5 "net/http" 6) 7 8func authMiddleware(f http.HandlerFunc) http.HandlerFunc { 9 return func(w http.ResponseWriter, r *http.Request) { 10 if token := r.Header.Get("token"); token != "fake_token" { 11 _, _ = w.Write([]byte("unauthorized\n")) 12 return 13 } 14 f(w, r) 15 } 16} 17 18func loggerMiddleware(f http.HandlerFunc) http.HandlerFunc { 19 return func(w http.ResponseWriter, r *http.Request) { 20 fmt.Println("before") 21 f(w, r) 22 fmt.Println("after") 23 } 24} 25 26type handler func(http.HandlerFunc) http.HandlerFunc 27 28// 聚合 handler 和 middleware 29func pipelineHandlers(h http.HandlerFunc, hs ...handler) http.HandlerFunc { 30 for i := range hs { 31 h = hs[i](h) 32 } 33 return h 34} 35 36func handleHello(w http.ResponseWriter, r *http.Request) { 37 fmt.Println("handle hello") 38 _, _ = w.Write([]byte("Hello World!\n")) 39} 40 41func main() { 42 http.HandleFunc("/hello", pipelineHandlers(handleHello, loggerMiddleware, authMiddleware)) 43 fmt.Println(http.ListenAndServe(":8888", nil)) 44}

我们借用 pipelineHandlers 函数将 handler 和 middleware 聚合到一起,实现了让这个简单的 Web Server 中间件用法跟 Gin 框架用法相似的效果。

再次启动 Server 进行验证:

img

img

改造成功,跟之前使用洋葱模型写法的结果如出一辙。

总结

简单了解了 Go 语言中如何实现装饰模式后,我们通过一个 Web Server 程序中间件,学习了装饰模式在 Go 语言中的应用。

需要注意的是,尽管 Go 语言实现的装饰器有类型上的限制,不如 Python 装饰器那般通用。就像我们最终实现的 pipelineHandlers 不如 Gin 框架中间件强大,比如不能延迟调用,通过 c.Next () 控制中间件调用流等。但不能因为这样就放弃,因为 GO 语言装饰器依然有它的用武之地。Go 语言是静态类型语言不像 Python 那般灵活,所以在实现上要多费一点力气。感兴趣的朋友可以在3A的云服务器上部署一套环境进行尝试练习希望通过这个简单的示例,相信对大家深入学习 Gin 框架有所帮助。

点赞
收藏

评论区

加载中...

相关推荐

Golang 常见设计模式之单例模式

之前我们已经看过了Golang常见设计模式中的装饰和选项模式,今天要看的是Golang设计模式里最简单的单例模式。单例模式的作用是确保无论对象被实例化多少次,全局都只有一个实例存在。根据这一特性,我们可以将其应用到全局唯一性配置、数据库连接对象、文件访问对象等。Go语言实现单例模式的方法有很多种,下面我们就一起来看一下。饿汉式饿汉式实现单例模式非

恶补了 Python 装饰器的六种写法,你随便问~

大家好,我是明哥。今天给大家分享一下关于装饰器的知识点,内容非常干,全程高能,认真吸收看完,一定会对装饰器有更深的理解。Hello,装饰器装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰

The Decorator 装饰器模式(JAVA)

\只是记录学习的点滴,错误请指出\\\1.装饰器模式能解决什么问题1:Responsibilitiesshouldbeaddedto(andremovedfrom)anobjectdynamicallyatruntime.2:Aflexibleal

Cocos Creator基础教程(13)—组件与节点的秘密

公众号上我们已经学习了有段时间的CocosCreator组件开发了,组件化开发到底有是什么?Shawn这阵子思绪不断,同时将自己的理解应用于实际项目中,颇有心得,在这里做一个分享。1\.节点与组件的关系有经验的同学,一上手CocosCreator就能想到「装饰模式」,我们看下定义:装饰模式(Deco

Java描述设计模式(09):装饰模式

本文源码:GitHub·点这里(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fgithub.com%2Fcicadasmile)||GitEE·点这里(https://gitee.com/cicadasmile)一、生活场景1、场景描述

Java Design Patterns

java的设计模式大体上分为三大类:创建型模式(5种):工厂方法模式,抽象工厂模式,单例模式,建造者模式,原型模式。结构型模式(7种):适配器模式,装饰器模式,代理模式,外观模式,桥接模式,组合模式,享元模式。行为型模式(11种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模