Patrón de decoración de patrones de diseño comunes en Golang

Presumiblemente, siempre que los estudiantes que estén familiarizados con Python estén familiarizados con el modo de decoración, este tipo de decorador admitido de forma nativa por la sintaxis de Python mejora en gran medida la aplicación del modo de decoración en Python. Aunque el patrón de decoración en Go no se usa tanto como en Python, tiene su propia singularidad. A continuación, echemos un vistazo a la aplicación del patrón de decoración en el lenguaje Go.

## Decorador sencillo

Echemos un vistazo a la aplicación simple del decorador a través de un ejemplo simple, primero escriba una función hola:


package main

import "fmt"

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

func main() {
    hello()
}
复制代码

Después de completar el código anterior, la ejecución generará "Hello World!". A continuación, agregue una línea de registro antes y después de imprimir "Hello World!" de la siguiente manera:

package main

import "fmt"

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

func main() {
    hello()
}
复制代码

Salida después de la ejecución del código:

before
Hello World!
after
复制代码

Por supuesto, podemos elegir una mejor implementación, que es escribir una función de registrador separada dedicada a imprimir registros.El ejemplo es el siguiente:

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()
}
复制代码

Puede ver que la función de registro recibe y devuelve una función, y la firma de la función de los parámetros y el valor devuelto es la misma que hola. Luego hacemos las siguientes modificaciones en la ubicación donde se llamó originalmente hello():

hello := logger(hello)
hello()
复制代码

De esta manera, envolvemos la función hello con la función logger para implementar de manera más elegante la función de agregar registros a la función hello. El resultado de la impresión después de la ejecución sigue siendo:

before
Hello World!
after
复制代码

De hecho, la función de registro es el decorador que usamos a menudo en Python, porque la función de registro se puede usar no solo para hola, sino también para cualquier otra función que tenga la misma firma que la función hola.

Por supuesto, si queremos usar la forma en que se escriben los decoradores en Python, podemos hacer esto:


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()
}
复制代码

Pero desafortunadamente, el programa anterior no se compila. Debido a que el lenguaje Go actualmente no brinda soporte para el azúcar sintáctico del decorador en el nivel sintáctico como el lenguaje Python.

Decorator implementa middleware

Aunque el decorador en Go no es tan conciso como Python, se usa ampliamente en componentes de middleware en escenarios de desarrollo web. Por ejemplo, el siguiente código del framework Gin Web definitivamente te resultará familiar siempre que lo hayas usado:

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

1.png

2.png

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

before
handle hello
after
复制代码

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

3.png

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

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

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

4.png

接下来我们要做的就是将 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 进行验证:

5.png

6.png

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

总结

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

需要注意的是,尽管 Go 语言实现的装饰器有类型上的限制,不如 Python 装饰器那般通用。就像我们最终实现的 pipelineHandlers 不如 Gin 框架中间件强大,比如不能延迟调用,通过 c.Next() 控制中间件调用流等。但不能因为这样就放弃,因为 GO 语言装饰器依然有它的用武之地。

Go 语言是静态类型语言不像 Python 那般灵活,所以在实现上要多费一点力气。希望通过这个简单的示例,相信对大家深入学习 Gin 框架有所帮助。

推荐阅读

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

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

Supongo que te gusta

Origin juejin.im/post/7078120798128439332
Recomendado
Clasificación