关于前端:Golang-常见设计模式之装饰模式

2次阅读

共计 4392 个字符,预计需要花费 11 分钟才能阅读完成。

想必只有是相熟 Python 的同学对装璜模式肯定不会生疏,这类 Python 从语法上原生反对的装璜器,大大提高了装璜模式在 Python 中的利用。只管 Go 语言中装璜模式没有 Python 中利用的那么宽泛,然而它也有其独到的中央。接下来就一起看下装璜模式在 Go 语言中的利用。

简略装璜器

咱们通过一个简略的例子来看一下装璜器的简略利用,首先编写一个 hello 函数:


package main

import "fmt"

func hello() {fmt.Println("Hello World!")
}

func main() {hello()
}

实现下面代码后,执行会输入“Hello World!”。接下来通过以下形式,在打印“Hello World!”前后各加一行日志:

package main

import "fmt"

func hello() {fmt.Println("before")
    fmt.Println("Hello World!")
    fmt.Println("after")
}

func main() {hello()
}

代码执行后输入:

before
Hello World!
after

当然咱们能够抉择一个更好的实现形式,即独自编写一个专门用来打印日志的 logger 函数,示例如下:

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() 的地位进行如下批改:

hello := logger(hello)
hello()

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

before
Hello World!
after

其实 logger 函数也就是咱们在 Python 中常常应用的装璜器,因为 logger 函数不仅能够用于 hello,还能够用于其余任何与 hello 函数有着同样签名的函数。

当然如果想应用 Python 中装璜器的写法,咱们能够这样做:


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 框架的如下代码,只有应用过就必定会感觉相熟:

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 函数逻辑:

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 来验证下装璜器:

对后果进行简略剖析能够看到,第一次申请 /hello 接口时,因为没有携带认证 token,收到了 unauthorized 响应。第二次申请时携带了 token,则失去响应“Hello World!”,并且后台程序打印如下日志:

before
handle hello
after

这阐明中间件执行程序是先由内向内进入,再由外向外返回。而这种一层一层包装解决逻辑的模型有一个十分形象且贴切的名字,洋葱模型。

但用洋葱模型实现的中间件有一个直观的问题。相比于 Gin 框架的中间件写法,这种一层层包裹函数的写法不如 Gin 框架提供的 r.Use(middlewares…) 写法直观。

Gin 框架源码的中间件和 handler 处理函数实际上被一起聚合到了路由节点的 handlers 属性中。其中 handlers 属性是 HandlerFunc 类型切片。对应到用 http 规范库实现的 Web Server 中,就是满足 func(ResponseWriter, *Request) 类型的 handler 切片。

当路由接口被调用时,Gin 框架就会像流水线一样顺次调用执行 handlers 切片中的所有函数,再顺次返回。这种思维也有一个形象的名字,就叫作流水线(Pipeline)。

接下来咱们要做的就是将 handleHello 和两个中间件 loggerMiddleware、authMiddleware 聚合到一起,同样造成一个 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 函数将 handler 和 middleware 聚合到一起,实现了让这个简略的 Web Server 中间件用法跟 Gin 框架用法类似的成果。

再次启动 Server 进行验证:

革新胜利,跟之前应用洋葱模型写法的后果一模一样。

总结

简略理解了 Go 语言中如何实现装璜模式后,咱们通过一个 Web Server 程序中间件,学习了装璜模式在 Go 语言中的利用。

须要留神的是,只管 Go 语言实现的装璜器有类型上的限度,不如 Python 装璜器那般通用。就像咱们最终实现的 pipelineHandlers 不如 Gin 框架中间件弱小,比方不能提早调用,通过 c.Next() 管制中间件调用流等。但不能因为这样就放弃,因为 GO 语言装璜器仍然有它的用武之地。

Go 语言是动态类型语言不像 Python 那般灵便,所以在实现上要多费一点力量。心愿通过这个简略的示例,置信对大家深刻学习 Gin 框架有所帮忙。

举荐浏览

两招晋升硬盘存储数据的写入效率

【程序员的实用工具举荐】Mac 效率神器 Alfred

正文完
 0