Go设计模式之责任链模式

共 13649字,需浏览 28分钟

 ·

2023-08-29 22:12


其实很多人不知道,责任链模式是我们工作中经常遇到的模式,特别是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 处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职责,所以叫作责任链模式。


6b64d200bc942628d249652bb5615da2.webp


(请双击图片查看)


2.模式demo


2.1 UML


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


1aa34b7d4f8744e1269cd10c843f38a3.webp


(请双 击图片查看 )


2.2 标准demo


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


838b42013b205a1dfb088e6099a158bf.webp


(请双 击图片查看 )


首先定义一个接口 IHandler


    


  1. type IHandler interface {


  2. SetNext ( handler IHandler )


  3. Handle ( score int )


  4. }



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


    


  1. type ConcreteHandler1 struct {


  2. Next IHandler


  3. }



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


  5. if score < 0 {


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


  7. return


  8. }


  9. if c . Next != nil {


  10. c . Next . Handle ( score )


  11. }


  12. return


  13. }


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


  15. c . Next = handler


  16. }



ConcreteHandler2


    


  1. type ConcreteHandler2 struct {


  2. Next IHandler


  3. }



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


  5. if score > 0 {


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


  7. return


  8. }


  9. if c . Next != nil {


  10. c . Next . Handle ( score )


  11. }


  12. return


  13. }



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


  15. c . Next = handler


  16. }



ConcreteHandler3


    


  1. type ConcreteHandler3 struct {


  2. Next IHandler


  3. }



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


  5. if score == 0 {


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


  7. return


  8. }


  9. if c . Next != nil {


  10. c . Next . Handle ( score )


  11. }


  12. return


  13. }



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


  15. c . Next = handler


  16. }



最后是 main函数:


    


  1. func main () {


  2. handler1 := & ConcreteHandler1 {}


  3. handler2 := & ConcreteHandler2 {}


  4. handler3 := & ConcreteHandler3 {}



  5. handler1 . SetNext ( handler2 )


  6. handler2 . SetNext ( handler3 )



  7. handler1 . Handle ( 10 )



  8. }



打印结果为:


    


  1. ConcreteHandler2 处理



2.3 改进版demo


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


80b6264e45481193676a5d4541c572e0.webp


(请双 击图片查看 )


对比上文的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. func ( c * ConcreteHandlerOne ) Handle ( score int ) {


  5. if score < 0 {


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


  7. return


  8. }


  9. }



ConcreteHandlerTwo


    


  1. type ConcreteHandlerTwo struct {


  2. Handler


  3. }



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


  5. if score > 0 {


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


  7. return


  8. }


  9. }



ConcreteHandlerThree


    


  1. type ConcreteHandlerThree struct {


  2. Handler


  3. }



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


  5. if score == 0 {


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


  7. return


  8. }


  9. }



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. }



最终的实现结构图:


9ef241a782994f749a40d0596d029322.webp


(请双 击图片查看 )


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


3. 源码解析


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


3.1 beego过滤器


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


d710f7d7ddba47eed0c5e09cc061e70d.webp


(请双 击图片查看 )


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. type FilterFunc = HandleFunc



3.1.3 Handler接口实现


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


    


  1. // 过滤器注册


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



  3. // 自定义过滤器


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


  5. isAccess := validateAccess ( ctx )


  6. if ! isAccess {


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


  8. ctx . WriteString ( string ( res ))


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


  10. }


  11. }



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. // the filter created by FilterChain


  10. chainRoot * FilterRouter



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


  12. filterChains [] filterChainConfig



  13. cfg * Config


  14. }




  15. type FilterRouter struct {


  16. filterFunc FilterFunc


  17. next * FilterRouter


  18. tree * Tree


  19. pattern string


  20. returnOnOutput bool


  21. resetParams bool


  22. }



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


3.2 Go源码http.handler


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


    


  1. package main



  2. import (


  3. "net/http"


  4. )



  5. func main () {


  6. s := http . NewServeMux ()



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



  8. // todo ....



  9. return


  10. })



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



  12. }



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


912cde37b9ba714fbb98d00f51b5fe3f.webp


(请双 击图片查看 )


3.2.1 client端


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


    


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


  2. ...



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


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


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


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


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


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


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


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



  11. ...



  12. }



可以看到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. // ServeHTTP calls f(w, r).


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


  8. f ( w , r )


  9. }



3.2.3 Handler接口实现


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


    


  1. func main () {


  2. s := http . NewServeMux ()



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



  4. // todo ....



  5. return


  6. })



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



  8. }



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


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


  11. if handler == nil {


  12. panic ( "http: nil handler" )


  13. }


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


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



  16. }



注意看上文的 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. type muxEntry struct {


  8. h Handler


  9. pattern string


  10. }



其中,用户自定以的处理函数都被封装到了 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. // *******寻找handler*******


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



  11. h . ServeHTTP ( w , r )


  12. }



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



  14. ...



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


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


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


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


  19. }



  20. // *******寻找handler*******


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


  22. }



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


  24. mux . mu . RLock ()


  25. defer mux . mu . RUnlock ()



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


  27. if mux . hosts {


  28. // *******寻找handler*******


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


  30. }


  31. if h == nil {


  32. // *******寻找handler*******


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


  34. }


  35. if h == nil {


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


  37. }


  38. return


  39. }




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



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


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


  43. if ok {


  44. return v . h , v . pattern


  45. }




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


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


  48. return e . h , e . pattern


  49. }


  50. }


  51. return nil , ""


  52. }



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


4. 总结


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


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



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


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







推荐阅读




福利

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


浏览 55
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报