golang 装饰器模式详解

前言

我一直以来对golang的装饰器模式情有独衷,不是因为它酷,而是它带给我了太多的好处。首先我不想说太多的概念,熟记这些概念对我的编程来说一点用处没有。我只知道它给我带来了好处,下面谈谈我的理解。

这种模式可以很轻松地把一些函数装配到另外一些函数上,让你的代码更加简单,也可以让一些“小功能型”的代码复用性更高,让代码中的函数可以像乐高玩具那样自由地拼装。

重要的是你不用修改代码以前的功能,对以前的功能没有影响,而是动态的,很方便的扩展函数的功能。下面我将举几个例子说明下

golang 的装饰器通常用interface{} 以及 anonymous functions 实现的,下面我们看看实际的例子

一、interface{} 实现装饰器

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!
}
  1. 在上面的代码中我们定义了一个Printer接口,一个 SimplePrinter 结构体实现了Print方法

  2. 我们定义了 BoldDecorator 函数接受一个Printer接口返回一个Printer接口.该函数将原来的 Print() 方法封装到一个新的方法中,该方法返回的是用 标记括起来的相同值

  3. 这只是一个简单的例子,却展示了装饰器的强大的功能。通过添加新的装饰器,我们可以在运行时改变对象的行为,而无需更改其原始代码。当我们需要为一个已经存在的对象添加新功能,而又想保持其原始代码不变时,装饰器模式就显得尤为有用。这样,我们就可以避免为每一个想要添加的新功能创建新的子类。

二、Http 相关的装饰器的例子

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)
   }
}
  1. 例子中 WithServerHeader,withServerSetCook,WithBasicAuth, WithDebugLog 就是一个装饰器,它传入一个 http.HandlerFunc 就是一个改写版本。而我们的业务hello不用修改任何功能,可以呈现出一些新的功能,很多人把这种模式称为middleware ,我更喜欢称为装饰器

三、多个装饰器Pipeline,也是options 模式

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 是常用的,在初始化配置函数经常用

你可能感兴趣的:(golang,设计模式,golang,设计模式,golang,golang,装饰器模式,装饰器模式)