本篇博客的视频教程首发于 Youtube:科技小飞哥,加入 电报粉丝群 获得最新视频更新和问题解答。

今天我们来介绍decorator这个经典的设计模式。 定义:装饰器模式主要对现有的类对象进行包裹和封装,以期望在不改变类对象及其类定义的情况下,为对象添加额外功能。是一种对象结构型模式。

Python装饰器模式

如果你熟悉Python,对装饰器模式应该不陌生。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
def wrapper(method):
    def inner(a, b):
        print("before...")
        ret = method(a, b)
        print("after...")
        return ret
    return inner

@wrapper
def add(a, b):
    print("run add")
    return a + b

ret = add(1,2)
//ret = wrapper(add)(1,2)
print(ret)

这就是最简单的装饰器模式,可以看到,在不改变add的定义的情况下,使用wrapperadd进行装饰。最终会输出:

before...
run add
after...
3

如果去掉line#15, 把line#20替换为line#21,其实效果是一样的,Python装饰器语法糖@wrapper, 在定义的时候就可以装饰函数,这样调用的时候就可以省略掉装饰函数。

Golang装饰器模式

在Golang中,装饰器(decorator)是一个这样的函数:它的参数是具体类型的函数,在装饰函数里面执行具体的函数之余,执行其他操作。

简单示例:

我们先来看一个最简单的示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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!")
}

是不是跟Python的装饰器一模一样,只不过Python支持@decorator语法糖,可以简化调用过程中的decorator。 这里你也可以这么写:

1
2
hello := decorator(Hello)
hello("Hello")

decorator是一个高阶函数,输入参数是一个函数,输出参数也是一个函数。就是装饰一个函数的作用,在不改变被装饰的函数的前提下,执行一些其他的操作。 这个示例看起来没什么作用,只是示范用,下面举几个在实际开发过程中常用的案例。

计算运行时间

 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
package main

import (
	"fmt"
	"reflect"
	"runtime"
	"time"
)

type WrapFunc func(int64, int64) int64

func getFunctionName(i interface{}) string {
	return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
}

func calculateRunTime(f WrapFunc) WrapFunc {
	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 Add(a, b int64) int64 {
	time.Sleep(100 * time.Millisecond)
	return a + b
}

func main() {
	Add := calculateRunTime(Add)
	Add(1, 2)
}

可以看到我们有任何一个函数Add, 需要计算函数执行时间,又不想修改函数本身,此时定义一个装饰器函数calculateRunTime,来修饰需要统计的函数Add。

HTTP

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

上面代码中使用到了修饰模式,WithServerHeader() 函数就是一个 Decorator,其传入一个 http.HandlerFunc,然后返回一个改写的版本。上面的例子还是比较简单,用 WithServerHeader() 就可以加入一个 Response 的 Header。