我一直以来对golang的装饰器模式情有独衷,不是因为它酷,而是它带给我了太多的好处。首先我不想说太多的概念,熟记这些概念对我的编程来说一点用处没有。我只知道它给我带来了好处,下面谈谈我的理解。
这种模式可以很轻松地把一些函数装配到另外一些函数上,让你的代码更加简单,也可以让一些“小功能型”的代码复用性更高,让代码中的函数可以像乐高玩具那样自由地拼装。
重要的是你不用修改代码以前的功能,对以前的功能没有影响,而是动态的,很方便的扩展函数的功能。下面我将举几个例子说明下
golang 的装饰器通常用interface{} 以及 anonymous functions 实现的,下面我们看看实际的例子
type Printer interface {
Print() string
}
type SimplePrinter struct {}
func (sp *SimplePrinter) Print() string {
return "Hello, world!"
}
func BoldDecorator(p Printer) Printer {
return PrinterFunc(func() string {
return "" + p.Print() + ""
})
}
type PrinterFunc func() string
func (pf PrinterFunc) Print() string {
return pf()
}
func main() {
simplePrinter := &SimplePrinter{}
boldPrinter := BoldDecorator(simplePrinter)
fmt.Println(simplePrinter.Print()) // Output: Hello, world!
fmt.Println(boldPrinter.Print()) // Output: Hello, world!
}
在上面的代码中我们定义了一个Printer接口,一个 SimplePrinter 结构体实现了Print方法
我们定义了 BoldDecorator 函数接受一个Printer接口返回一个Printer接口.该函数将原来的 Print() 方法封装到一个新的方法中,该方法返回的是用 标记括起来的相同值
这只是一个简单的例子,却展示了装饰器的强大的功能。通过添加新的装饰器,我们可以在运行时改变对象的行为,而无需更改其原始代码。当我们需要为一个已经存在的对象添加新功能,而又想保持其原始代码不变时,装饰器模式就显得尤为有用。这样,我们就可以避免为每一个想要添加的新功能创建新的子类。
func WithServerHeader(h http.HandlerFunc) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
writer.Header().Set("server", "0.01")
h(writer, request)
}
}
func withServerSetCook(h http.HandlerFunc) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
cookie := http.Cookie{Name: "username", Value: "tt"}
http.SetCookie(writer, &cookie)
h(writer, request)
}
}
func WithBasicAuth(h http.HandlerFunc) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
cookie, err := request.Cookie("username")
if err != nil || cookie.Value != "ee" {
writer.WriteHeader(http.StatusForbidden)
return
}
h(writer, request)
}
}
func WithDebugLog(h http.HandlerFunc) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
request.ParseForm()
log.Println(request.Form)
log.Println("path", request.URL.Path)
log.Println("Host", request.URL.Host)
log.Println(request.Form["url_long"])
for k, v := range request.Form {
log.Println("key:", k)
log.Println("value:", v)
}
h(writer, request)
}
}
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("/hello/v1", WithServerHeader(hello))
http.HandleFunc("/hello/v2", withServerSetCook(hello))
http.HandleFunc("/hello/v3", WithBasicAuth(hello))
http.HandleFunc("/hello/v4", WithDebugLog(hello))
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
例子中 WithServerHeader,withServerSetCook,WithBasicAuth, WithDebugLog 就是一个装饰器,它传入一个 http.HandlerFunc 就是一个改写版本。而我们的业务hello不用修改任何功能,可以呈现出一些新的功能,很多人把这种模式称为middleware ,我更喜欢称为装饰器
type googleSlide struct {
sreSlide *list.List
interval int64
mutex sync.Mutex
k int64
}
type slideVal struct {
time int64
req int64
accept int64
}
type SlideValOptions func(val *slideVal)
func NewSlideval(options ...SlideValOptions) *slideVal {
t := &slideVal{
time: time.Now().UnixNano(),
}
for _, option := range options {
option(t)
}
return t
}
func WithReqOption(req int64) SlideValOptions {
return func(val *slideVal) {
val.req = req
}
}
func WithAcceptReqOption(accept int64) SlideValOptions {
return func(val *slideVal) {
val.accept = accept
}
}
func NewGoogleSlide(interval time.Duration, k int64) *googleSlide {
return &googleSlide{
sreSlide: list.New(),
interval: interval.Nanoseconds(),
k: k,
}
}
func (g *googleSlide) Sre() grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
g.mutex.Lock()
now := time.Now().UnixNano()
front := g.sreSlide.Front()
for front != nil && front.Value.(*slideVal).time+g.interval < now {
g.sreSlide.Remove(front)
front = g.sreSlide.Front()
}
var r, accept int64
front = g.sreSlide.Front()
for front != nil {
t := front.Value.(*slideVal)
r += t.req
accept += t.accept
front = front.Next()
}
tail := (float64(r) - float64(g.k*accept)) / (float64(r) + 1)
if tail > 0 {
g.mutex.Unlock()
return errors.New("request is fail")
}
g.sreSlide.PushBack(NewSlideval(WithReqOption(1)))
err := invoker(ctx, method, req, req, cc, opts...)
if err == nil {
g.sreSlide.PushBack(NewSlideval(WithAcceptReqOption(1)))
}
g.mutex.Unlock()
return err
}
}
这个代码是我在SRE 熔断器写的代码,现在重新拿出来,是因为具有代表性 。NewSlideval 可以支持多个装饰器,遍历装饰器,就可以得倒我们想要的功能,只要我们去实现这个装饰器。这样的代码,在golang 是常用的,在初始化配置函数经常用