Go装饰器实现

简介: Go装饰器实现

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 -


相关文章
|
2月前
|
Go
go函数
go函数
33 10
|
2月前
|
Go
Go to Learn Go之反射
Go to Learn Go之反射
41 8
|
2月前
|
Go
Go to Learn Go之错误处理
Go to Learn Go之错误处理
44 7
|
2月前
|
Go
Go to Learn Go之作用域
Go to Learn Go之作用域
24 5
|
5月前
|
Go
go基础语法结束篇 ——函数与方法
go基础语法结束篇 ——函数与方法
|
6月前
|
存储 Java 编译器
|
6月前
|
存储 NoSQL Go
|
6月前
|
缓存 编译器 测试技术
|
Unix Go
go10 函数
go10 函数
87 0
|
Go
Go | 闭包的使用
Go | 闭包的使用
110 0
Go | 闭包的使用