go责任链模式

发布时间 2023-08-28 18:31:22作者: 技术颜良

其实很多人不知道,责任链模式是我们工作中经常遇到的模式,特别是web后端工程师,我们工作中每时每刻都在用:因为市面上大部分的web框架的过滤器基本都是基于这个设计模式为基本模式搭建的。

1.模式介绍

我们先来看一下责任链模式(Chain Of Responsibility Design Pattern )的英文介绍:Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.

翻译成中文就是:将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止。

这么说比较抽象,用更加容易理解的话来进一步解读一下。在责任链模式中,一个请求过来,会有多个处理器(也就是刚刚定义中说的“接收对象”)依次处理同一个请求。即请求先经过 A 处理器处理,然后再把请求传递给 B 处理器,B 处理器处理完后再传递给 C 处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职责,所以叫作责任链模式。

图片

(请双击图片查看)

2.模式demo

2.1 UML

责任链模式(Chain Of Responsibility Design Pattern )的整体结构如下:

图片

(请双击图片查看)

2.2 标准demo

我们依据标准的UML图,写出一个具体的例子(对应UML图):

图片

(请双击图片查看)

首先定义一个接口 IHandler

  1. type IHandler interface {

  2. SetNext(handler IHandler)

  3. Handle(score int)

  4. }

然后分别构建三个不同的实现: ConcreteHandler1

  1. type ConcreteHandler1 struct {

  2. Next IHandler

  3. }

  4.  

  5. func (c *ConcreteHandler1) Handle(score int) {

  6. if score < 0 {

  7. fmt.Println("ConcreteHandler1 处理")

  8. return

  9. }

  10. if c.Next != nil {

  11. c.Next.Handle(score)

  12. }

  13. return

  14. }

  15. func (c *ConcreteHandler1) SetNext(handler IHandler) {

  16. c.Next = handler

  17. }

ConcreteHandler2

  1. type ConcreteHandler2 struct {

  2. Next IHandler

  3. }

  4.  

  5. func (c *ConcreteHandler2) Handle(score int) {

  6. if score > 0 {

  7. fmt.Println("ConcreteHandler2 处理")

  8. return

  9. }

  10. if c.Next != nil {

  11. c.Next.Handle(score)

  12. }

  13. return

  14. }

  15.  

  16. func (c *ConcreteHandler2) SetNext(handler IHandler) {

  17. c.Next = handler

  18. }

ConcreteHandler3

  1. type ConcreteHandler3 struct {

  2. Next IHandler

  3. }

  4.  

  5. func (c *ConcreteHandler3) Handle(score int) {

  6. if score == 0 {

  7. fmt.Println("ConcreteHandler3 处理")

  8. return

  9. }

  10. if c.Next != nil {

  11. c.Next.Handle(score)

  12. }

  13. return

  14. }

  15.  

  16. func (c *ConcreteHandler3) SetNext(handler IHandler) {

  17. c.Next = handler

  18. }

最后是 main函数:

  1. func main() {

  2. handler1 := &ConcreteHandler1{}

  3. handler2 := &ConcreteHandler2{}

  4. handler3 := &ConcreteHandler3{}

  5.  

  6. handler1.SetNext(handler2)

  7. handler2.SetNext(handler3)

  8.  

  9. handler1.Handle(10)

  10.  

  11. }

打印结果为:

  1. ConcreteHandler2 处理

2.3 改进版demo

通过以上标准例子不难发现: main函数承接了很多client自身之外的“额外工作”:构建和拼接组装责任链,这不利于后续client端的使用和扩展:一不小心可能责任链拼就接错了或者拼接少节点了。我们可以对UML做一个改进:增加一个节点管理模块。改进图如下:

图片

(请双击图片查看)

对比上文的uml图,新增加了一个 ChainHandler结构体用来管理拼接的 Handler,client端无需了解 Handler的业务, Handler的组合可以使用链表,也可以使用数组(当前用了数组)。具体实现如下:先定义 Handler接口:

  1. type Handler interface {

  2. Handle(score int)

  3. }

然后分别实现 Handler接口的三个结构体: ConcreteHandlerOne

  1. type ConcreteHandlerOne struct {

  2. Handler

  3. }

  4.  

  5. func (c *ConcreteHandlerOne) Handle(score int) {

  6. if score < 0 {

  7. fmt.Println("ConcreteHandler1 处理")

  8. return

  9. }

  10. }

ConcreteHandlerTwo

  1. type ConcreteHandlerTwo struct {

  2. Handler

  3. }

  4.  

  5. func (c *ConcreteHandlerTwo) Handle(score int) {

  6. if score > 0 {

  7. fmt.Println("ConcreteHandler2 处理")

  8. return

  9. }

  10. }

ConcreteHandlerThree

  1. type ConcreteHandlerThree struct {

  2. Handler

  3. }

  4.  

  5. func (c *ConcreteHandlerThree) Handle(score int) {

  6. if score == 0 {

  7. fmt.Println("ConcreteHandler3 处理")

  8. return

  9. }

  10. }

main函数调用(client调用):

  1. func main() {

  2. chain := &ChainHandler{}

  3. chain.AddHandler(&ConcreteHandlerOne{})

  4. chain.AddHandler(&ConcreteHandlerTwo{})

  5. chain.AddHandler(&ConcreteHandlerThree{})

  6. chain.Handle(10)

  7. }

最终的实现结构图:

图片

(请双击图片查看)

日常工作中出现的责任链模式(Chain Of Responsibility Design Pattern )一般都是以上这种包含 Hanlder管理的模式。

3. 源码解析

在日常框架和语言基础库中,经常能够看到很多场景使用了责任链模式。

3.1 beego过滤器

可以对比改进版demo的uml图,beego的过滤器就是按照这种模式来设计的(当前参照的beego版本是2.0)。

图片

(请双击图片查看)

3.1.1 client端

调用端首先是过滤器的注册:

  1. web.InsertFilter("/v2/api/*", web.BeforeRouter, auth.AuthAPIFilter)

然后在 github.com/beego/beego/v2@v2.0.3/server/web/router.go的 ControllerRegister结构体的 serveHttp函数中

  1. if len(p.filters[BeforeRouter]) > 0 && p.execFilter(ctx, urlPath, BeforeRouter) {

  2. goto Admin

  3. }

以上 p.execFilter(ctx,urlPath,BeforeRouter)处,启动调用。

3.1.2 Handler接口

Handler接口很简单

  1. // HandleFunc define how to process the request

  2. type HandleFunc func(ctx *beecontext.Context)

  3.  

  4. ...

  5.  

  6. type FilterFunc = HandleFunc

3.1.3 Handler接口实现

接口的实现扩展比较灵活,直接把用户定义的函数作为接口的实现。与client端中的过滤器注册联动。

  1. // 过滤器注册

  2. web.InsertFilter("/v2/api/*", web.BeforeRouter, auth.AuthAPIFilter)

  3.  

  4. // 自定义过滤器

  5. var AuthAPIFilter = func(ctx *context.Context) {

  6. isAccess := validateAccess(ctx)

  7. if !isAccess {

  8. res, _ := json.Marshal(r)

  9. ctx.WriteString(string(res))

  10. // ctx.Redirect(401, "/401")

  11. }

  12. }

3.1.4 Handler管理

Handler的管理模块是在 github.com/beego/beego/v2@v2.0.3/server/web/router.go的中的 FilterRouter和 ControllerRegister两个结构体中

  1. // ControllerRegister containers registered router rules, controller handlers and filters.

  2. type ControllerRegister struct {

  3. routers map[string]*Tree

  4. enablePolicy bool

  5. enableFilter bool

  6. policies map[string]*Tree

  7. filters [FinishRouter + 1][]*FilterRouter

  8. pool sync.Pool

  9.  

  10. // the filter created by FilterChain

  11. chainRoot *FilterRouter

  12.  

  13. // keep registered chain and build it when serve http

  14. filterChains []filterChainConfig

  15.  

  16. cfg *Config

  17. }

  18.  

  19.  

  20. type FilterRouter struct {

  21. filterFunc FilterFunc

  22. next *FilterRouter

  23. tree *Tree

  24. pattern string

  25. returnOnOutput bool

  26. resetParams bool

  27. }

FilterRouter是一个链表,包含用户自定义的过滤函数; ControllerRegister对 FilterRouter进行管理。

3.2 Go源码http.handler

我们在使用Go构建http web服务器的时候,使用的http.Handler就是使用的责任链模式。

  1. package main

  2.  

  3. import (

  4. "net/http"

  5. )

  6.  

  7. func main() {

  8. s := http.NewServeMux()

  9.  

  10. s.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {

  11.  

  12. // todo ....

  13.  

  14. return

  15. })

  16.  

  17. http.ListenAndServe(":80", s)

  18.  

  19. }

以 2.3的UML图为标准,整体的对照结构图如下:

图片

(请双击图片查看)

3.2.1 client端

整个模式的启动是随着http server启动后,接受到请求后的处理开始的。在 net/http/server.go的 serve函数中

  1. func (c *conn) serve(ctx context.Context) {

  2. ...

  3.  

  4. // HTTP cannot have multiple simultaneous active requests.[*]

  5. // Until the server replies to this request, it can't read another,

  6. // so we might as well run the handler in this goroutine.

  7. // [*] Not strictly true: HTTP pipelining. We could let them all process

  8. // in parallel even if their responses need to be serialized.

  9. // But we're not going to implement HTTP pipelining because it

  10. // was never deployed in the wild and the answer is HTTP/2.

  11. serverHandler{c.server}.ServeHTTP(w, w.req)

  12.  

  13. ...

  14.  

  15. }

可以看到http server的原理很简单,就是for 死循环等待接收,然后一个请求过来,就对应的生成一个单独的协程 goroutine去处理。

3.2.2 Handler接口

Go源码中对责任链模式的实现非常标准,Handler接口与设计模式中的Handler接口同名,在 net/http/server.go中:

  1. type Handler interface {

  2. ServeHTTP(ResponseWriter, *Request)

  3. }

为了扩展方便,在使用过程中并非直接使用,而是中间又加了一层抽象层(相当于Java中的抽象类了,Go中没有抽象类)

  1. // The HandlerFunc type is an adapter to allow the use of

  2. // ordinary functions as HTTP handlers. If f is a function

  3. // with the appropriate signature, HandlerFunc(f) is a

  4. // Handler that calls f.

  5. type HandlerFunc func(ResponseWriter, *Request)

  6.  

  7. // ServeHTTP calls f(w, r).

  8. func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {

  9. f(w, r)

  10. }

3.2.3 Handler接口实现

与上文中提到的Beego的过滤器类似,Go的Handler设计的也非常容易扩展,用户自定义的请求处理函数Handler都会变成 Handler的子类。

  1. func main() {

  2. s := http.NewServeMux()

  3.  

  4. s.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {

  5.  

  6. // todo ....

  7.  

  8. return

  9. })

  10.  

  11. http.ListenAndServe(":80", s)

  12.  

  13. }

  14.  

  15. // HandleFunc registers the handler function for the given pattern.

  16. func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {

  17. if handler == nil {

  18. panic("http: nil handler")

  19. }

  20. // 强制类型转换,转成了实现了Hanlder的“抽象类”HandlerFunc

  21. mux.Handle(pattern, HandlerFunc(handler))

  22.  

  23. }

注意看上文的 HandleFunc中的 mux.Handle(pattern,HandlerFunc(handler))这一行,将用户自定义的处理函数强制转换成了上文3.2.2中的 Handler的"抽象类" HandlerFunc类型,进而实现了继承。

3.2.4 Handler接口的管理类ChainHandler

Go中对Handler的管理类是在 net/http/server.go文件的 ServeMux结构体和 muxEntry结构体中:

  1. type ServeMux struct {

  2. mu sync.RWMutex

  3. m map[string]muxEntry

  4. es []muxEntry // slice of entries sorted from longest to shortest.

  5. hosts bool // whether any patterns contain hostnames

  6. }

  7.  

  8. type muxEntry struct {

  9. h Handler

  10. pattern string

  11. }

其中,用户自定以的处理函数都被封装到了 muxEntry结构体的 Handler中,一个自定义的函数对应一个 muxEntry, ServeMux使用hashmap对 muxEntry集合进行管理(上文的beego中是使用的链表,上文demo中使用了数组)。当web server接收到请求的时候, ServeMux会根据hashmap找到相应的handler然后处理。

  1. func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {

  2. if r.RequestURI == "*" {

  3. if r.ProtoAtLeast(1, 1) {

  4. w.Header().Set("Connection", "close")

  5. }

  6. w.WriteHeader(StatusBadRequest)

  7. return

  8. }

  9.  

  10. // *******寻找handler*******

  11. h, _ := mux.Handler(r)

  12.  

  13. h.ServeHTTP(w, r)

  14. }

  15.  

  16. func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {

  17.  

  18. ...

  19.  

  20. if path != r.URL.Path {

  21. _, pattern = mux.handler(host, path)

  22. u := &url.URL{Path: path, RawQuery: r.URL.RawQuery}

  23. return RedirectHandler(u.String(), StatusMovedPermanently), pattern

  24. }

  25.  

  26. // *******寻找handler*******

  27. return mux.handler(host, r.URL.Path)

  28. }

  29.  

  30. func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {

  31. mux.mu.RLock()

  32. defer mux.mu.RUnlock()

  33.  

  34. // Host-specific pattern takes precedence over generic ones

  35. if mux.hosts {

  36. // *******寻找handler*******

  37. h, pattern = mux.match(host + path)

  38. }

  39. if h == nil {

  40. // *******寻找handler*******

  41. h, pattern = mux.match(path)

  42. }

  43. if h == nil {

  44. h, pattern = NotFoundHandler(), ""

  45. }

  46. return

  47. }

  48.  

  49.  

  50. func (mux *ServeMux) match(path string) (h Handler, pattern string) {

  51.  

  52. // ********通过hashmap找到相关handler*********

  53. v, ok := mux.m[path]

  54. if ok {

  55. return v.h, v.pattern

  56. }

  57.  

  58.  

  59. for _, e := range mux.es {

  60. if strings.HasPrefix(path, e.pattern) {

  61. return e.h, e.pattern

  62. }

  63. }

  64. return nil, ""

  65. }

在程序运行过程中,用户注册自定义的函数被转化成了 Handler,然后 Handler又结合用户自定义的 URL地址被 ServeMux以 URL为Key、 Handler为Value做成hashmap管理起来;等到请求来的时候, ServeMux就根据用户请求的 URL地址,从hashmap中找到具体的 Hanlder来处理请求。

4. 总结

责任链模式的基本思想就是要处理的请求(通常会是结构体,然后作为函数参数);依次经过多个处理对象处理,这些处理函数可以动态的添加和删除,具备很高的灵活性和扩展性,通常会对这些处理函数做统一处理,存储方式一般是通过链表、数组、hash map等存储结构。

责任链模式的应用非常广泛:

  1. 业务场景:作为敏感词(涉黄、政治、反动等此)过滤的设计结构

  2. 技术框架:路由、router过滤器、日志log框架等等



推荐阅读

 

福利
我为大家整理了一份从入门到进阶的Go学习资料礼包,包含学习建议:入门看什么,进阶看什么。关注公众号 「polarisxu」,回复 ebook 获取;还可以回复「进群」,和数万 Gopher 交流学习。