• golang 装饰器模式详解


    前言

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

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

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

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

    一、interface{} 实现装饰器

    1. type Printer interface {
    2.    Print() string
    3. }
    4. type SimplePrinter struct {}
    5. func (sp *SimplePrinter) Print() string {
    6.    return "Hello, world!"
    7. }
    8. func BoldDecorator(p Printer) Printer {
    9.    return PrinterFunc(func() string {
    10.        return "" + p.Print() + ""
    11.   })
    12. }
    13. type PrinterFunc func() string
    14. func (pf PrinterFunc) Print() string {
    15.    return pf()
    16. }
    17. func main() {
    18.    simplePrinter := &SimplePrinter{}
    19.    boldPrinter := BoldDecorator(simplePrinter)
    20.    fmt.Println(simplePrinter.Print()) // Output: Hello, world!
    21.    fmt.Println(boldPrinter.Print()) // Output: Hello, world!
    22. }
    1. 在上面的代码中我们定义了一个Printer接口,一个 SimplePrinter 结构体实现了Print方法

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

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

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

    1. func WithServerHeader(h http.HandlerFunc) http.HandlerFunc {
    2.   return func(writer http.ResponseWriter, request *http.Request) {
    3.      writer.Header().Set("server", "0.01")
    4.      h(writer, request)
    5.   }
    6. }
    7. func withServerSetCook(h http.HandlerFunc) http.HandlerFunc {
    8.   return func(writer http.ResponseWriter, request *http.Request) {
    9.      cookie := http.Cookie{Name: "username", Value: "tt"}
    10.      http.SetCookie(writer, &cookie)
    11.      h(writer, request)
    12.   }
    13. }
    14. func WithBasicAuth(h http.HandlerFunc) http.HandlerFunc {
    15.   return func(writer http.ResponseWriter, request *http.Request) {
    16.      cookie, err := request.Cookie("username")
    17.      if err != nil || cookie.Value != "ee" {
    18.         writer.WriteHeader(http.StatusForbidden)
    19.         return
    20.     }
    21.      h(writer, request)
    22.   }
    23. }
    24. func WithDebugLog(h http.HandlerFunc) http.HandlerFunc {
    25.   return func(writer http.ResponseWriter, request *http.Request) {
    26.      request.ParseForm()
    27.      log.Println(request.Form)
    28.      log.Println("path", request.URL.Path)
    29.      log.Println("Host", request.URL.Host)
    30.      log.Println(request.Form["url_long"])
    31.      for k, v := range request.Form {
    32.         log.Println("key:", k)
    33.         log.Println("value:", v)
    34.     }
    35.      h(writer, request)
    36.   }
    37. }
    38. func hello(w http.ResponseWriter, r *http.Request) {
    39.   log.Printf("Recieved Request %s from %s\n", r.URL.Path, r.RemoteAddr)
    40.   fmt.Fprintf(w, "Hello, World! "+r.URL.Path)
    41. }
    42. func main() {
    43.   http.HandleFunc("/hello/v1", WithServerHeader(hello))
    44.   http.HandleFunc("/hello/v2", withServerSetCook(hello))
    45.   http.HandleFunc("/hello/v3", WithBasicAuth(hello))
    46.   http.HandleFunc("/hello/v4", WithDebugLog(hello))
    47.   err := http.ListenAndServe(":8080", nil)
    48.   if err != nil {
    49.      log.Fatal("ListenAndServe: ", err)
    50.   }
    51. }
    1. 例子中 WithServerHeader,withServerSetCook,WithBasicAuth, WithDebugLog 就是一个装饰器,它传入一个 http.HandlerFunc 就是一个改写版本。而我们的业务hello不用修改任何功能,可以呈现出一些新的功能,很多人把这种模式称为middleware ,我更喜欢称为装饰器

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

    1. type googleSlide struct {
    2. sreSlide *list.List
    3. interval int64
    4. mutex sync.Mutex
    5. k int64
    6. }
    7. type slideVal struct {
    8. time int64
    9. req int64
    10. accept int64
    11. }
    12. type SlideValOptions func(val *slideVal)
    13. func NewSlideval(options ...SlideValOptions) *slideVal {
    14. t := &slideVal{
    15. time: time.Now().UnixNano(),
    16. }
    17. for _, option := range options {
    18. option(t)
    19. }
    20. return t
    21. }
    22. func WithReqOption(req int64) SlideValOptions {
    23. return func(val *slideVal) {
    24. val.req = req
    25. }
    26. }
    27. func WithAcceptReqOption(accept int64) SlideValOptions {
    28. return func(val *slideVal) {
    29. val.accept = accept
    30. }
    31. }
    32. func NewGoogleSlide(interval time.Duration, k int64) *googleSlide {
    33. return &googleSlide{
    34. sreSlide: list.New(),
    35. interval: interval.Nanoseconds(),
    36. k: k,
    37. }
    38. }
    39. func (g *googleSlide) Sre() grpc.UnaryClientInterceptor {
    40. return func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
    41. g.mutex.Lock()
    42. now := time.Now().UnixNano()
    43. front := g.sreSlide.Front()
    44. for front != nil && front.Value.(*slideVal).time+g.interval < now {
    45. g.sreSlide.Remove(front)
    46. front = g.sreSlide.Front()
    47. }
    48. var r, accept int64
    49. front = g.sreSlide.Front()
    50. for front != nil {
    51. t := front.Value.(*slideVal)
    52. r += t.req
    53. accept += t.accept
    54. front = front.Next()
    55. }
    56. tail := (float64(r) - float64(g.k*accept)) / (float64(r) + 1)
    57. if tail > 0 {
    58. g.mutex.Unlock()
    59. return errors.New("request is fail")
    60. }
    61. g.sreSlide.PushBack(NewSlideval(WithReqOption(1)))
    62. err := invoker(ctx, method, req, req, cc, opts...)
    63. if err == nil {
    64. g.sreSlide.PushBack(NewSlideval(WithAcceptReqOption(1)))
    65. }
    66. g.mutex.Unlock()
    67. return err
    68. }
    69. }

    这个代码是我在SRE 熔断器写的代码,现在重新拿出来,是因为具有代表性 。NewSlideval 可以支持多个装饰器,遍历装饰器,就可以得倒我们想要的功能,只要我们去实现这个装饰器。这样的代码,在golang 是常用的,在初始化配置函数经常用

  • 相关阅读:
    Brain Teaser概率类 - 抛硬币
    Python数据分析训练营——Python数据分析之Numpy
    @Change监听事件与vue监听属性:watch的区别?
    什么是springMVC 视图和视图解析器
    洛谷千题详解 | P1004 [NOIP2000 提高组] 方格取数【C++、Java、Pascal语言】
    估算tps
    手把手入门Node框架Egg.js
    再突破!阿里云进入Gartner云AI开发者服务挑战者象限
    milvus集合管理
    容器化 | 在 S3 实现定时备份
  • 原文地址:https://blog.csdn.net/xingjigongsi/article/details/136304208