Go装饰器实现
1. 目录
- 介绍装饰器在Go中的实现
- 装饰器在http中的使用
- 装饰器实现Pipeline
- Pipeline在框架中的应用
2. 介绍装饰器在Go中的实现
写过Python
的人都知道,它的“糖”很多,尤其在写装饰器的时候非常简单优雅,比如:
from functools import wraps def decorator(func): @wraps(func) # @wraps接受一个函数来进行装饰,并加入了复制函数名称、注释文档、参数列表等等的功能。这可以让我们在装饰器里面访问在装饰之前的函数的属性。 def decorated(*args, **kwargs): print("start") result = func(*args, **kwargs) # 这里直接调用原函数 并没有修改他 只是在调用之前和之后增加了额外的处理逻辑 print("end") return result return decorated @decorator # 用装饰器装饰add 这种@语法糖确实很香啊 def add(x): return x + x print(add(4)) # 直接调用add
结果:
start end 8
其实装饰器是一种非常优秀的设计模式,它将重复的内容抽象出来,赋予一个函数其他的功能,但又不去改变函数自身。使得代码极其简洁,易于维护。
在Go
中如何实现装饰器呢?其实很简单,只需要利用高阶函数就可以实现,看代码:
package main import ( "fmt" ) // 为函数类型设置别名提高代码可读性 type add func(int) int // 求和 func addFunc(a int) int { return a + a } // 通过高阶函数在不侵入原有函数实现的前提下计算函数之和 func decorator(f add) add { return func(a int) int { fmt.Println("start") c := f(a) // 直接调用 fmt.Println("end") return c // 返回计算结果 } } func main() { a := 4 // 通过修饰器调用求和函数,返回的是一个匿名函数 deco := decorator(addFunc) // 执行修饰器返回函数 c := deco(a) fmt.Println(c) }
结果:
start end 8
我们可以看到装饰器模式是遵循SOLID设计原则中的开放封闭原则的,即对扩展开放,对修改关闭。
3. 装饰器在http中的使用
import ( "fmt" "log" "net/http" ) //装饰器 func WithServerHeader(h http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { log.Println("--->WithServerHeader()") w.Header().Set("Server", "HelloServer") 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("/api/v1/hello", WithServerHeader(hello)) // 装饰器装饰hello err := http.ListenAndServe(":8080", nil) if err != nil { log.Fatal("ListenAndServe: ", err) } }
这样的写法比较常见哈,我们在写自己的http服务的时候经常这样使用,其实本质上就是一种装饰器模式,它给你带来的便捷不止于此,请往下看装饰器实现Pipeline
。
4. 装饰器实现Pipeline
上面那个http例子,假如现在有多个装饰器都要装饰hello服务,那么写法可能是这样的:
... 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 main() { http.HandleFunc("/api/v1/hello", WithServerHeader(WithAuthCookie(hello))) // 多个装饰器装饰hello err := http.ListenAndServe(":8080", nil) if err != nil { log.Fatal("ListenAndServe: ", err) } }
但是没发现吗,这样的写法层层嵌套并不优雅,如果装饰器非常多,那就非常难看,如何避免这种写法呢?有的,请看:
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 }
然后我们就可以这样使用了:
http.HandleFunc("/api/v1/hello", Handler(hello, WithServerHeader, WithBasicAuth, WithDebugLog))
其实这样的方式也就是Pipeline的实现模式
。
5. Pipeline在框架中的应用
我们只看一个框架gin
,gin中的中间件就是用Pipeline
实现的,而Pipeline的实现就是多个装饰器实现的。
type HandlersChain []HandlerFunc func (c HandlersChain) Last() HandlerFunc { if length := len(c); length > 0 { return c[length-1] } return nil } //设置中间件 func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes { group.Handlers = append(group.Handlers, middleware...) return group.returnObj() } 因为中间件也是HandlerFunc, 可以当作一个handler来处理。
- END -