装饰器
装饰器(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)
}
}
ToUpper和ToMd5都接受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/http的http.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中,函数不支持设置默认参数,可以使用类似装饰器的方法来设置。
看下面的例子,通过WithNum和WithString来指定参数。
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