Golang的修饰器模式

2,750 阅读2分钟

装饰器

装饰器(decorator)是一个这样的函数:它的参数是具体类型的函数,并且返回值也是和参数相同类型的函数。 看下面的例子:

type StringOperator func(string) string

func ident(s string) string {
	return s
}

func ToUpper(m StringOperator) StringOperator {
	return func(s string) string {
		lower := strings.ToUpper(s)
		return m(lower)
	}
}

func ToMd5(m StringOperator) StringOperator {
	return func(s string) string {
		h := md5.New()
		h.Write([]byte(s))
		b64 := base64.StdEncoding.EncodeToString(h.Sum(nil))
		return m(b64)

	}
}

ToUpperToMd5都接受func(string) string作为参数,并且返回和参数相同的类型func(string) string。 调用情况:

func TestDecorator1(t *testing.T) {
	s := "Hello, World"

	var fn1 StringOperator = ident
	fn1 = ToMd5(ToUpper(ident))
	fmt.Println(fn1(s))

	var fn2 StringOperator = ident
	fn2 = ToUpper(ToMd5(fn2))
	fmt.Println(fn2(s))
}

net/httphttp.HandleFunc也用到了装饰器模式。

type HandlerFunc func(ResponseWriter, *Request)

调用者可以自己设置http的调用链。

func hello(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello, World! "+r.URL.Path)
}

func WithLog(h http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		log.Printf("Recieved Request %s from %s\n", r.URL.Path, r.RemoteAddr)
		h(w, r)
	}
}

func TestHttp(t *testing.T) {
	http.HandleFunc("/hello", WithLog(hello))
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

装饰器的流水线(Pipeline)

有时候,多层的调用可能会导致代码不好阅读,例如ToMd5(ToUpper(ident))。这时候可以改成:

type Decorator func(StringOperator) StringOperator

func Handler(m StringOperator, decorators ...Decorator) StringOperator {
	for i := len(decorators) - 1; i > 0; i-- {
		m = decorators[i](m)
	}
	return m
}

func TestDecorator(t *testing.T) {
	s := "Hello, World"
	fn := Handler(ident, ToUpper, ToMd5)

    fmt.Println(fn(s))
}

设置默认参数

Golang中,函数不支持设置默认参数,可以使用类似装饰器的方法来设置。 看下面的例子,通过WithNumWithString来指定参数。

type Param struct {
	p1 int
	p2 OptionParam
}

type OptionParam struct {
	a int
	b string
}

func defaultOptionParam() OptionParam {
	option := OptionParam{
		a: 10,
		b: "const",
	}
	return option
}

type SetOption func(option *OptionParam)

func WithNum(num int) SetOption {
	return func(option *OptionParam) {
		option.a = num
	}
}

func WithString(str string) SetOption {
	return func(option *OptionParam) {
		option.b = str
	}
}

func SetParams(p1 int, setOptions ...SetOption) Param {
	option := defaultOptionParam() // set default value in the beginning
	// custom
	for _, set := range setOptions {
		set(&option)
	}

	return Param{
		p1: p1,
		p2: option,
	}
}

我的公众号:lyp_share

我的知乎专栏

我的博客com

我的博客cn