我一直以来对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 是常用的,在初始化配置函数经常用