Python decorators, which are syntactically native to Python, have greatly improved the use of decorators in Python. Although the decorative pattern in Go is not as widely used as it is in Python, it has its own unique features. Let’s take a look at the use of decoration patterns in Go.
Simple decorator
Let’s take a look at the simple use of decorators with a simple example. We’ll start with a hello function:
package main import "fmt" func hello() { fmt.Println("Hello World!" ) } func main() { hello() }Copy the code
After completing the above code, the execution prints “Hello World!” . Next, print “Hello World! Add one line before and one line after:
package main import "fmt" func hello() { fmt.Println("before") fmt.Println("Hello World!" ) fmt.Println("after") } func main() { hello() }Copy the code
Output after code execution:
before
Hello World!
after
Copy the code
A better way to do this is to write a separate logger function for logging, as shown in the following example:
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() }Copy the code
You can see that the Logger function receives and returns a function whose parameters and return values have the same function signature as Hello. We then change hello() from where we called it:
hello := logger(hello)
hello()
Copy the code
In this way, we use logger function to wrap Hello function, more elegant implementation of hello function log function. The output is still as follows:
before
Hello World!
after
Copy the code
Logger is actually a decorator that we use a lot in Python, because it can be used not only for Hello, but for any other function that has the same signature as Hello.
Of course, if we want to write decorators in Python, we can do this:
package main import "fmt" func logger(f func()) func() { return func() { fmt.Println("before") f() fmt.Println("after") }} @logger func hello() {fmt.println ("Hello World!" } func main() {// Hello is called the same way hello()}Copy the code
Unfortunately, the above program does not compile. Because Go does not currently provide support for decorator syntactic sugar at the syntactic level that Python does.
Decorators implement middleware
Although not as concise as Python, Go decorators are widely used in middleware components in Web development scenarios. For example, the following code for the Gin Web Framework will be familiar to anyone who has used it:
Package main import "github.com/gin-gonic/gin" func main() {r := gin.New() // Use middleware. gin.Recovery()) r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) _ = r.Run(":8888") }Copy the code
In the gin framework, r.us (middlewares…) can be used, just as in the example of adding logs using ging.logger () and handling panic exceptions using ging.recovery (). Add a lot of middleware to the route, so that we can intercept the route processing function and do some processing logic before and after it.
The Gin framework middleware is implemented using the decorator pattern. Let’s use Go’s HTTP library for a simple simulation. This is a simple Web Server application 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)) }Copy the code
We wrapped handleHello with loggerMiddleware and authMiddleware functions to print access logs and verify authentication, respectively. If we need to add additional middleware interception capabilities, we can do this with unlimited wrapping.
Start the Server to validate the decorator:
An unauthorized response is received when the/Hello interface is requested for the first time because it does not carry an authentication token. The second request carries the token and gets the response “Hello World!” And the background program prints the following log:
before
handle hello
after
Copy the code
This indicates that the order of middleware execution is first from the outside in, and then from the inside out back. This layer-by-layer model of processing logic is aptly named the Onion model.
But there is an intuitive problem with middleware implemented with the Onion model. Middlewares (MIDDlewares…) middlewares (MIDDlewares…) middlewares (MIDDlewares…) middlewares (Middlewares…) middlewares (Middlewares…) middlewares (Middlewares…) middlewares (Middlewares…) middlewares So this is intuitive.
The Gin framework source middleware and handler handler functions are actually aggregated together into the Handlers property of the routing node. The Handlers attribute is a HandlerFunc slice. Corresponding to the HTTP standard library implementation of the Web Server, is the func(ResponseWriter, *Request) type of handler slice.
When the routing interface is invoked, the Gin framework pipelines all functions in the Handlers slice and returns. This idea also has an image name, called Pipeline.
The next step is to aggregate handleHello and two middleware, loggerMiddleware and authMiddleware, into a Pipeline as well.
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 // Aggregate handler and 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)) }Copy the code
We use the pipelineHandlers function to aggregate handler and middleware together to achieve the effect of making this simple Web Server middleware usage similar to Gin framework usage.
Start the Server again for verification:
The transformation was successful, as was the case with the onion model.
conclusion
After a brief understanding of how to implement decoration pattern in Go language, we learned the application of decoration pattern in Go language through a Web Server program middleware.
It is important to note that although the Go language implements decorators with type limitations, they are not as generic as Python decorators. The pipelineHandlers we eventually implemented are not as powerful as the Gin framework middleware, such as not being able to defer calls, controlling the middleware call flow via C.next (), etc. But don’t let that stop you, because the GO language decorator still has its place.
Go is a statically typed language and not as flexible as Python, so it takes a bit more implementation effort. I hope this simple example will help you learn more about the Gin framework.
Recommended reading
Two methods to improve disk data writing efficiency
Alfred, the Mac Productivity wizard