Before wrote a “Python decorator, functional programming, this model can easily get on some function fitting to some other function, can make your code more simple, also can make some small” functional “code reusability is higher, make the function in the code can be freely assembled like lego. Therefore, I have always been interested in the programming mode of decoration. Here I will write an article about Go language.
Those of you who read the Python decorator article know that this is a form of functional programming — wrapping a higher-order function around it. Say more, about functional programming, you can see before I wrote an article “functional programming”, this article mainly, through from procedural programming way of thinking to functional programming way of thinking, to drive more people to play functional programming, so if you want to know about the functional programming, you can walk to read first. So, Go’s modifier programming pattern is actually the pattern of functional programming.
Note, however, that Go doesn’t have much sugar and is a strongly typed, static, virtual-free language, so you can’t do decorator code as elegant as Java and Python. Of course, I may be the uneducated, if you know of more, please do let me know. Thanks in advance.
A simple example
Let’s start with an example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
package main
import "fmt"
func decorator(f func(s string)) func(s string) {
return func(s string) {
fmt.Println( "Started" )
f(s)
fmt.Println( "Done" )
}
}
func Hello(s string) {
fmt.Println(s)
}
func main() {
decorator(Hello)( "Hello, World!" )
}
|
We can see that we are using a higher-order decorator() that passes in Hello() and returns an anonymous function that calls Hello() as well as its own code.
This is similar to Python, except that, sadly, Go doesn’t support @decorator syntactic sugar like Python does. So, it’s kind of ugly to call. Of course, if you want to make your code easier to read, you can do this:
1
2
|
hello := decorator(Hello)
hello( "Hello" )
|
Let’s look at another example of calculating the running time:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
package main
import (
"fmt"
"reflect"
"runtime"
"time"
)
type SumFunc func(int64, int64) int64
func getFunctionName(i interface{}) string {
return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
}
func timedSumFunc(f SumFunc) SumFunc {
return func(start, end int64) int64 {
defer func(t time .Time) {
fmt.Printf( "--- Time Elapsed (%s): %v ---\n" .
getFunctionName(f), time .Since(t))
} ( time .Now())
return f(start, end)
}
}
func Sum1(start, end int64) int64 {
var sum int64
sum = 0
if start > end {
start, end = end, start
}
for i := start; i <= end; i++ {
sum += i
}
return sum
}
func Sum2(start, end int64) int64 {
if start > end {
start, end = end, start
}
return (end - start + 1) * (end + start) / 2
}
func main() {
sum1 := timedSumFunc(Sum1)
sum2 := timedSumFunc(Sum2)
fmt.Printf( "%d, %d\n" , sum1(-10000, 10000000), sum2(-10000, 10000000))
}
|
A few things to say about the above code:
1) There are two Sum functions, Sum1() is a simple loop, and Sum2() uses a data formula. (Note: start and end can have negative numbers)
2) The code uses the Go language reflection machine to get the function name.
3) The modifier function is timedSumFunc()
Output after running:
1
2
3
4
|
$ go run time . sum .go
-- Time Elapsed (main.sum1): Elapsed (main.sum1): Elapsed () --
--- Time Elapsed (main.Sum2): 291ns ---
49999954995000, 49999954995000
|
Call waiting welfare
1. Recently sorted out 20G resources, including product/operation/test/programmer/market, etc., and Internet practitioners [necessary skills for work, professional books on the industry, precious books on interview questions, etc.]. Access:
-
Scan the code of wechat to follow the public account “Atypical Internet”, forward the article to the moments of friends, and send the screenshots to the background of the public account to obtain dry goods resources links;
2. Internet Communication Group:
-
Pay attention to the public account “atypical Internet”, in the background of the public account reply “into the group”, network sharing, communication;
An example related to HTTP
Let’s look at a related example of handling an HTTP request.
Let’s start with a simple HTTP Server code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
package main
import (
"fmt"
"log"
"net/http"
"strings"
)
func WithServerHeader(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log .Println( "--->WithServerHeader()" )
w.Header().Set( "Server" . "HelloServer v0.0.1" )
h(w, r)
}
}
func hello(w http.ResponseWriter, r *http.Request) {
log .Printf( "Recieved Request %s from %s\n" , r.URL.Path, r.RemoteAddr)
fmt.Fprintf(w, "Hello, World! " +r.URL.Path)
}
func main() {
http.HandleFunc( "/v1/hello" , WithServerHeader(hello))
err := http.ListenAndServe( ": 8080" , nil)
if err ! = nil {
log .Fatal( "ListenAndServe: " , err)
}
}
|
The Decorator pattern is used in the code above. The WithServerHeader() function is a Decorator that passes in an HTTP.handlerfunc and returns an overwritten version. The above example is still relatively simple, adding a Response Header with WithServerHeader().
So, we can write several of these functions. Some write HTTP response headers, some write authentication cookies, some check authentication cookies, some log…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
package main
import (
"fmt"
"log"
"net/http"
"strings"
)
func WithServerHeader(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log .Println( "--->WithServerHeader()" )
w.Header().Set( "Server" . "HelloServer v0.0.1" )
h(w, r)
}
}
func WithAuthCookie(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log .Println( "--->WithAuthCookie()" )
cookie := &http.Cookie{Name: "Auth" , Value: "Pass" , Path: "/" }
http.SetCookie(w, cookie)
h(w, r)
}
}
func WithBasicAuth(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log .Println( "--->WithBasicAuth()" )
cookie, err := r.Cookie( "Auth" )
if err ! = nil || cookie.Value ! = "Pass" {
w.WriteHeader(http.StatusForbidden)
return
}
h(w, r)
}
}
func WithDebugLog(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log .Println( "--->WithDebugLog" )
r.ParseForm()
log .Println(r.Form)
log .Println( "path" , r.URL.Path)
log .Println( "scheme" , r.URL.Scheme)
log .Println(r.Form[ "url_long" ])
for k, v := range r.Form {
log .Println( "key:" , k)
log .Println( "val:" , strings.Join(v, "" ))
}
h(w, r)
}
}
func hello(w http.ResponseWriter, r *http.Request) {
log .Printf( "Recieved Request %s from %s\n" , r.URL.Path, r.RemoteAddr)
fmt.Fprintf(w, "Hello, World! " +r.URL.Path)
}
func main() {
http.HandleFunc( "/v1/hello" , WithServerHeader(WithAuthCookie(hello)))
http.HandleFunc( "/v2/hello" , WithServerHeader(WithBasicAuth(hello)))
http.HandleFunc( "/v3/hello" , WithServerHeader(WithBasicAuth(WithDebugLog(hello))))
err := http.ListenAndServe( ": 8080" , nil)
if err ! = nil {
log .Fatal( "ListenAndServe: " , err)
}
}
|
Pipeline for multiple modifiers
In practice, the need to layer functions on top of each other doesn’t look very good, and the code will look ugly if you need more decorators. Well, we can refactor that.
To refactor, we need to write a utility function that iterates through and calls each decorator:
1
2
3
4
5
6
7
8
9
|
type HttpHandlerDecorator func(http.HandlerFunc) http.HandlerFunc
func Handler(h http.HandlerFunc, decors ... HttpHandlerDecorator) http.HandlerFunc {
for i := range decors {
d := decors[len(decors)-1-i] // iterate in reverse
h = d(h)
}
return h
}
|
Then, we can use it like this.
1
2
|
http.HandleFunc( "/v4/hello" , Handler(hello,
WithServerHeader, WithBasicAuth, WithDebugLog))
|
Isn’t this code a little more readable? The pipeline function comes out.
Modifiers for generic types
However, for Go the decorator pattern, there is a small problem – as if unable to do a generic, like the above the calculation function of time, the code needs to be modified function coupling interface type, can’t do is very general, if it doesn’t solve, so, the decorator pattern is a bit bad to use.
Because Unlike Python and Java, Python is a dynamic language, and Java has a language virtual machine, so they can do a lot of kinky things, whereas Go is a static language, which means that its types need to be fixed at compile time, otherwise it won’t compile. However, the largest generics supported by the Go language are interface{} and the simpler Reflection mechanism, which should still work.
Without further ado, here is a more general modifier I wrote using reflection (I have removed the error determination code for ease of reading)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
func Decorator(decoPtr, fn interface{}) (err error) {
var decoratedFunc, targetFunc reflect.Value
decoratedFunc = reflect.ValueOf(decoPtr).Elem()
targetFunc = reflect.ValueOf(fn)
v := reflect.MakeFunc(targetFunc.Type(),
func(in []reflect.Value) (out []reflect.Value) {
fmt.Println( "before" )
out = targetFunc.Call(in)
fmt.Println( "after" )
return
})
decoratedFunc.Set(v)
return
}
|
The above code invokes reflect.makefunc () to create a new function where targetfunc.call (in) calls the decorated function. I recommend The official article “The Laws of Reflection” for The Go language, which I won’t Go into here.
The Decorator() above takes two arguments,
- The first one is the output parameter
decoPtr
, is the modified function - The second is the input parameter
fn
, is the function that needs to be decorated
Isn’t that a little silly? It is. However, this is the best code I could write personally in the Go language. If you know more elegant, please do let me know!
Ok, let’s see how it works. First suppose we have two functions that need to be decorated:
1
2
3
4
5
6
7
8
9
|
func foo(a, b, c int ) int {
fmt.Printf( "%d, %d, %d \n" , a, b, c)
return a + b + c
}
func bar(a, b string) string {
fmt.Printf( "%s, %s \n" , a, b)
return a + b
}
|
Then, we can do this:
1
2
3
4
|
type MyFoo func( int . int . int ) int
var myfoo MyFoo
Decorator(&myfoo, foo)
myfoo(1, 2, 3)
|
You’ll notice how silly it feels to use a Decorator() and declare a function signature first. It’s not generic at all, is it?
Well. If you don’t want to declare a function signature, you can
1
2
3
|
mybar := bar
Decorator(&mybar, bar)
mybar( "hello," . "world!" )
|
Well, it doesn’t look pretty, but it works. Go doesn’t seem to have the same features as Java or Python right now, so we can only ask for more sugar!