8
头图

Presumably, as long as students who are familiar with Python are familiar with decoration mode, this kind of decorator natively supported by Python syntax greatly improves the application of decoration mode in Python. Although the decoration pattern in Go is not as widely used as in Python, it does have its own uniqueness. Next, let's take a look at the application of the decoration pattern in the Go language.

simple decorator

Let's take a look at the simple application of the decorator through a simple example, first write a hello function:


package main

import "fmt"

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

func main() {
    hello()
}

After completing the above code, the execution will output "Hello World!". Next, add a log line before and after printing "Hello World!" in the following way:

package main

import "fmt"

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

func main() {
    hello()
}

Output after code execution:

before
Hello World!
after

Of course, we can choose a better implementation, which is to write a separate logger function dedicated to printing logs. The example is as follows:

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()
}

You can see that the logger function receives and returns a function, and the function signature of the parameters and return value is the same as hello. Then we make the following modifications in the location where hello() was originally called:

hello := logger(hello)
hello()

In this way, we wrap the hello function with the logger function to more elegantly implement the function of adding logs to the hello function. The print result after execution is still:

before
Hello World!
after

In fact, the logger function is the decorator we often use in Python, because the logger function can be used not only for hello, but also for any other function that has the same signature as the hello function.

Of course, if we want to use the way decorators are written in Python, we can do this:


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()
}

But unfortunately, the above program fails to compile. Because Go language currently does not provide support for decorator syntactic sugar at the syntactic level like Python language.

Decorator implements middleware

Although the decorator in Go is not as concise as Python, it is widely used in middleware components in web development scenarios. For example, the following code of the Gin Web framework will definitely feel familiar as long as you have used it:

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")
}

For example, use gin.Logger() to add logs and use gin.Recovery() to handle panic exceptions. In the Gin framework, you can add a lot of middleware to routing by r.Use(middlewares...), It is convenient for us to intercept the routing processing function and do some processing logic before and after it.

The middleware of the Gin framework is implemented using the decoration pattern. Let's use the http library that comes with the Go language to perform a simple simulation. This is a simple Web Server program that listens on port 8888 and enters the handleHello function logic when accessing the /hello route:

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))
}

We use the loggerMiddleware and authMiddleware functions to wrap handleHello respectively, so that it supports printing access logs and authentication verification functions. If we also need to add other middleware interception functions, we can do infinite packaging in this way.

Start the server to verify the decorator:

A simple analysis of the results shows that when the /hello interface was requested for the first time, an unauthorized response was received because the authentication token was not carried. When the token is carried in the second request, the response "Hello World!" is obtained, and the background program prints the following log:

before
handle hello
after

This shows that the middleware execution sequence is to enter from the outside to the inside first, and then return from the inside to the outside. And this model that wraps processing logic layer by layer has a very vivid and apt name, the onion model.

But middleware implemented with the onion model has an intuitive problem. Compared with the middleware writing method of the Gin framework, the writing method of this layer-by-layer wrapping function is not as intuitive as the writing method of r.Use(middlewares...) provided by the Gin framework.

The middleware and handler processing functions of the Gin framework source code are actually aggregated together into the handlers attribute of the routing node. Where the handlers property is a slice of type HandlerFunc. Corresponding to the Web Server implemented by the http standard library, it is the handler slice that satisfies the type of func(ResponseWriter, *Request).

When the routing interface is called, the Gin framework will call and execute all the functions in the handlers slice in turn like a pipeline, and then return in turn. This kind of thinking also has an image name, which is called Pipeline.

What we need to do next is to aggregate handleHello and two middleware loggerMiddleware and authMiddleware together to form a 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))
}

We use the pipelineHandlers function to aggregate the handler and middleware together to achieve the effect of making this simple Web Server middleware usage similar to the Gin framework usage.

Start the Server again to verify:

The transformation is successful, which is exactly the same as the result of using the onion model writing method before.

Summarize

After a brief understanding of how to implement the decoration pattern in the Go language, we learned the application of the decoration pattern in the Go language through a Web Server program middleware.

It should be noted that although the decorators implemented by the Go language have type restrictions, they are not as general as Python decorators. Just like the pipelineHandlers we finally implemented is not as powerful as the Gin framework middleware, for example, it cannot delay the call, and control the middleware call flow through c.Next(). But don't give up because of this, because GO language decorators still have their place.

The Go language is statically typed and not as flexible as Python, so it takes a little more effort to implement. I hope that through this simple example, I believe it will be helpful for you to learn the Gin framework in depth.

Recommended reading

Two tricks to improve the writing efficiency of hard disk storage data

[Programmer's practical tool recommendation] Mac efficiency artifact Alfred


云叔_又拍云
5.9k 声望4.6k 粉丝

又拍云是专注CDN、云存储、小程序开发方案、 短视频开发方案、DDoS高防等产品的国内知名企业级云服务商。