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 处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职责,所以叫作责任链模式。
(请双击图片查看)
2.模式demo
2.1 UML
责任链模式(Chain Of Responsibility Design Pattern )的整体结构如下:
(请双 击图片查看 )
2.2 标准demo
我们依据标准的UML图,写出一个具体的例子(对应UML图):
(请双 击图片查看 )
首先定义一个接口 IHandler
:
-
type IHandler interface {
-
SetNext ( handler IHandler )
-
Handle ( score int )
-
}
然后分别构建三个不同的实现: ConcreteHandler1
-
type ConcreteHandler1 struct {
-
Next IHandler
-
}
-
-
func ( c * ConcreteHandler1 ) Handle ( score int ) {
-
if score < 0 {
-
fmt . Println ( "ConcreteHandler1 处理" )
-
return
-
}
-
if c . Next != nil {
-
c . Next . Handle ( score )
-
}
-
return
-
}
-
func ( c * ConcreteHandler1 ) SetNext ( handler IHandler ) {
-
c . Next = handler
-
}
ConcreteHandler2
-
type ConcreteHandler2 struct {
-
Next IHandler
-
}
-
-
func ( c * ConcreteHandler2 ) Handle ( score int ) {
-
if score > 0 {
-
fmt . Println ( "ConcreteHandler2 处理" )
-
return
-
}
-
if c . Next != nil {
-
c . Next . Handle ( score )
-
}
-
return
-
}
-
-
func ( c * ConcreteHandler2 ) SetNext ( handler IHandler ) {
-
c . Next = handler
-
}
ConcreteHandler3
-
type ConcreteHandler3 struct {
-
Next IHandler
-
}
-
-
func ( c * ConcreteHandler3 ) Handle ( score int ) {
-
if score == 0 {
-
fmt . Println ( "ConcreteHandler3 处理" )
-
return
-
}
-
if c . Next != nil {
-
c . Next . Handle ( score )
-
}
-
return
-
}
-
-
func ( c * ConcreteHandler3 ) SetNext ( handler IHandler ) {
-
c . Next = handler
-
}
最后是 main
函数:
-
func main () {
-
handler1 := & ConcreteHandler1 {}
-
handler2 := & ConcreteHandler2 {}
-
handler3 := & ConcreteHandler3 {}
-
-
handler1 . SetNext ( handler2 )
-
handler2 . SetNext ( handler3 )
-
-
handler1 . Handle ( 10 )
-
-
}
打印结果为:
-
ConcreteHandler2 处理
2.3 改进版demo
通过以上标准例子不难发现: main
函数承接了很多client自身之外的“额外工作”:构建和拼接组装责任链,这不利于后续client端的使用和扩展:一不小心可能责任链拼就接错了或者拼接少节点了。我们可以对UML做一个改进:增加一个节点管理模块。改进图如下:
(请双 击图片查看 )
对比上文的uml图,新增加了一个 ChainHandler
结构体用来管理拼接的 Handler
,client端无需了解 Handler
的业务, Handler
的组合可以使用链表,也可以使用数组(当前用了数组)。具体实现如下:先定义 Handler
接口:
-
type Handler interface {
-
Handle ( score int )
-
}
然后分别实现 Handler
接口的三个结构体: ConcreteHandlerOne
-
type ConcreteHandlerOne struct {
-
Handler
-
}
-
-
func ( c * ConcreteHandlerOne ) Handle ( score int ) {
-
if score < 0 {
-
fmt . Println ( "ConcreteHandler1 处理" )
-
return
-
}
-
}
ConcreteHandlerTwo
-
type ConcreteHandlerTwo struct {
-
Handler
-
}
-
-
func ( c * ConcreteHandlerTwo ) Handle ( score int ) {
-
if score > 0 {
-
fmt . Println ( "ConcreteHandler2 处理" )
-
return
-
}
-
}
ConcreteHandlerThree
-
type ConcreteHandlerThree struct {
-
Handler
-
}
-
-
func ( c * ConcreteHandlerThree ) Handle ( score int ) {
-
if score == 0 {
-
fmt . Println ( "ConcreteHandler3 处理" )
-
return
-
}
-
}
main
函数调用(client调用):
-
func main () {
-
chain := & ChainHandler {}
-
chain . AddHandler (& ConcreteHandlerOne {})
-
chain . AddHandler (& ConcreteHandlerTwo {})
-
chain . AddHandler (& ConcreteHandlerThree {})
-
chain . Handle ( 10 )
-
}
最终的实现结构图:
(请双 击图片查看 )
日常工作中出现的责任链模式(Chain Of Responsibility Design Pattern )一般都是以上这种包含 Hanlder
管理的模式。
3. 源码解析
在日常框架和语言基础库中,经常能够看到很多场景使用了责任链模式。
3.1 beego过滤器
可以对比改进版demo的uml图,beego的过滤器就是按照这种模式来设计的(当前参照的beego版本是2.0)。
(请双 击图片查看 )
3.1.1 client端
调用端首先是过滤器的注册:
-
web . InsertFilter ( "/v2/api/*" , web . BeforeRouter , auth . AuthAPIFilter )
然后在 github.com/beego/beego/v2@v2.0.3/server/web/router.go
的 ControllerRegister
结构体的 serveHttp
函数中
-
if len ( p . filters [ BeforeRouter ]) > 0 && p . execFilter ( ctx , urlPath , BeforeRouter ) {
-
goto Admin
-
}
以上 p.execFilter(ctx,urlPath,BeforeRouter)
处,启动调用。
3.1.2 Handler接口
Handler接口很简单
-
// HandleFunc define how to process the request
-
type HandleFunc func ( ctx * beecontext . Context )
-
-
...
-
-
type FilterFunc = HandleFunc
3.1.3 Handler接口实现
接口的实现扩展比较灵活,直接把用户定义的函数作为接口的实现。与client端中的过滤器注册联动。
-
// 过滤器注册
-
web . InsertFilter ( "/v2/api/*" , web . BeforeRouter , auth . AuthAPIFilter )
-
-
// 自定义过滤器
-
var AuthAPIFilter = func ( ctx * context . Context ) {
-
isAccess := validateAccess ( ctx )
-
if ! isAccess {
-
res , _ := json . Marshal ( r )
-
ctx . WriteString ( string ( res ))
-
// ctx.Redirect(401, "/401")
-
}
-
}
3.1.4 Handler管理
Handler
的管理模块是在 github.com/beego/beego/v2@v2.0.3/server/web/router.go
的中的 FilterRouter
和 ControllerRegister
两个结构体中
-
// ControllerRegister containers registered router rules, controller handlers and filters.
-
type ControllerRegister struct {
-
routers map [ string ]* Tree
-
enablePolicy bool
-
enableFilter bool
-
policies map [ string ]* Tree
-
filters [ FinishRouter + 1 ][]* FilterRouter
-
pool sync . Pool
-
-
// the filter created by FilterChain
-
chainRoot * FilterRouter
-
-
// keep registered chain and build it when serve http
-
filterChains [] filterChainConfig
-
-
cfg * Config
-
}
-
-
-
type FilterRouter struct {
-
filterFunc FilterFunc
-
next * FilterRouter
-
tree * Tree
-
pattern string
-
returnOnOutput bool
-
resetParams bool
-
}
FilterRouter
是一个链表,包含用户自定义的过滤函数; ControllerRegister
对 FilterRouter
进行管理。
3.2 Go源码http.handler
我们在使用Go构建http web服务器的时候,使用的http.Handler就是使用的责任链模式。
-
package main
-
-
import (
-
"net/http"
-
)
-
-
func main () {
-
s := http . NewServeMux ()
-
-
s . HandleFunc ( "/" , func ( writer http . ResponseWriter , request * http . Request ) {
-
-
// todo ....
-
-
return
-
})
-
-
http . ListenAndServe ( ":80" , s )
-
-
}
以 2.3的UML图
为标准,整体的对照结构图如下:
(请双 击图片查看 )
3.2.1 client端
整个模式的启动是随着http server启动后,接受到请求后的处理开始的。在 net/http/server.go
的 serve
函数中
-
func ( c * conn ) serve ( ctx context . Context ) {
-
...
-
-
// HTTP cannot have multiple simultaneous active requests.[*]
-
// Until the server replies to this request, it can't read another,
-
// so we might as well run the handler in this goroutine.
-
// [*] Not strictly true: HTTP pipelining. We could let them all process
-
// in parallel even if their responses need to be serialized.
-
// But we're not going to implement HTTP pipelining because it
-
// was never deployed in the wild and the answer is HTTP/2.
-
serverHandler { c . server }. ServeHTTP ( w , w . req )
-
-
...
-
-
}
可以看到http server的原理很简单,就是for 死循环等待接收,然后一个请求过来,就对应的生成一个单独的协程 goroutine
去处理。
3.2.2 Handler接口
Go源码中对责任链模式的实现非常标准,Handler接口与设计模式中的Handler接口同名,在 net/http/server.go
中:
-
type Handler interface {
-
ServeHTTP ( ResponseWriter , * Request )
-
}
为了扩展方便,在使用过程中并非直接使用,而是中间又加了一层抽象层(相当于Java中的抽象类了,Go中没有抽象类)
-
// The HandlerFunc type is an adapter to allow the use of
-
// ordinary functions as HTTP handlers. If f is a function
-
// with the appropriate signature, HandlerFunc(f) is a
-
// Handler that calls f.
-
type HandlerFunc func ( ResponseWriter , * Request )
-
-
// ServeHTTP calls f(w, r).
-
func ( f HandlerFunc ) ServeHTTP ( w ResponseWriter , r * Request ) {
-
f ( w , r )
-
}
3.2.3 Handler接口实现
与上文中提到的Beego的过滤器类似,Go的Handler设计的也非常容易扩展,用户自定义的请求处理函数Handler都会变成 Handler
的子类。
-
func main () {
-
s := http . NewServeMux ()
-
-
s . HandleFunc ( "/" , func ( writer http . ResponseWriter , request * http . Request ) {
-
-
// todo ....
-
-
return
-
})
-
-
http . ListenAndServe ( ":80" , s )
-
-
}
-
-
// HandleFunc registers the handler function for the given pattern.
-
func ( mux * ServeMux ) HandleFunc ( pattern string , handler func ( ResponseWriter , * Request )) {
-
if handler == nil {
-
panic ( "http: nil handler" )
-
}
-
// 强制类型转换,转成了实现了Hanlder的“抽象类”HandlerFunc
-
mux . Handle ( pattern , HandlerFunc ( handler ))
-
-
}
注意看上文的 HandleFunc
中的 mux.Handle(pattern,HandlerFunc(handler))
这一行,将用户自定义的处理函数强制转换成了上文3.2.2中的 Handler
的"抽象类" HandlerFunc
类型,进而实现了继承。
3.2.4 Handler接口的管理类ChainHandler
Go中对Handler的管理类是在 net/http/server.go
文件的 ServeMux
结构体和 muxEntry
结构体中:
-
type ServeMux struct {
-
mu sync . RWMutex
-
m map [ string ] muxEntry
-
es [] muxEntry // slice of entries sorted from longest to shortest.
-
hosts bool // whether any patterns contain hostnames
-
}
-
-
type muxEntry struct {
-
h Handler
-
pattern string
-
}
其中,用户自定以的处理函数都被封装到了 muxEntry
结构体的 Handler
中,一个自定义的函数对应一个 muxEntry
, ServeMux
使用hashmap对 muxEntry
集合进行管理(上文的beego中是使用的链表,上文demo中使用了数组)。当web server接收到请求的时候, ServeMux
会根据hashmap找到相应的handler然后处理。
-
func ( mux * ServeMux ) ServeHTTP ( w ResponseWriter , r * Request ) {
-
if r . RequestURI == "*" {
-
if r . ProtoAtLeast ( 1 , 1 ) {
-
w . Header (). Set ( "Connection" , "close" )
-
}
-
w . WriteHeader ( StatusBadRequest )
-
return
-
}
-
-
// *******寻找handler*******
-
h , _ := mux . Handler ( r )
-
-
h . ServeHTTP ( w , r )
-
}
-
-
func ( mux * ServeMux ) Handler ( r * Request ) ( h Handler , pattern string ) {
-
-
...
-
-
if path != r . URL . Path {
-
_ , pattern = mux . handler ( host , path )
-
u := & url . URL { Path : path , RawQuery : r . URL . RawQuery }
-
return RedirectHandler ( u . String (), StatusMovedPermanently ), pattern
-
}
-
-
// *******寻找handler*******
-
return mux . handler ( host , r . URL . Path )
-
}
-
-
func ( mux * ServeMux ) handler ( host , path string ) ( h Handler , pattern string ) {
-
mux . mu . RLock ()
-
defer mux . mu . RUnlock ()
-
-
// Host-specific pattern takes precedence over generic ones
-
if mux . hosts {
-
// *******寻找handler*******
-
h , pattern = mux . match ( host + path )
-
}
-
if h == nil {
-
// *******寻找handler*******
-
h , pattern = mux . match ( path )
-
}
-
if h == nil {
-
h , pattern = NotFoundHandler (), ""
-
}
-
return
-
}
-
-
-
func ( mux * ServeMux ) match ( path string ) ( h Handler , pattern string ) {
-
-
// ********通过hashmap找到相关handler*********
-
v , ok := mux . m [ path ]
-
if ok {
-
return v . h , v . pattern
-
}
-
-
-
for _ , e := range mux . es {
-
if strings . HasPrefix ( path , e . pattern ) {
-
return e . h , e . pattern
-
}
-
}
-
return nil , ""
-
}
在程序运行过程中,用户注册自定义的函数被转化成了 Handler
,然后 Handler
又结合用户自定义的 URL
地址被 ServeMux
以 URL
为Key、 Handler
为Value做成hashmap管理起来;等到请求来的时候, ServeMux
就根据用户请求的 URL
地址,从hashmap中找到具体的 Hanlder
来处理请求。
4. 总结
责任链模式的基本思想就是要处理的请求(通常会是结构体,然后作为函数参数);依次经过多个处理对象处理,这些处理函数可以动态的添加和删除,具备很高的灵活性和扩展性,通常会对这些处理函数做统一处理,存储方式一般是通过链表、数组、hash map等存储结构。
责任链模式的应用非常广泛:
-
业务场景:作为敏感词(涉黄、政治、反动等此)过滤的设计结构
-
技术框架:路由、router过滤器、日志log框架等等
推荐阅读
我为大家整理了一份 从入门到进阶的Go学习资料礼包 ,包含学习建议:入门看什么,进阶看什么。 关注公众号 「polarisxu」,回复 ebook 获取;还可以回复「进群」,和数万 Gopher 交流学习。