Go設(shè)計(jì)模式之責(zé)任鏈模式
其實(shí)很多人不知道,責(zé)任鏈模式是我們工作中經(jīng)常遇到的模式,特別是web后端工程師,我們工作中每時(shí)每刻都在用:因?yàn)槭忻嫔洗蟛糠值膚eb框架的過濾器基本都是基于這個(gè)設(shè)計(jì)模式為基本模式搭建的。
1.模式介紹
我們先來看一下責(zé)任鏈模式(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.
翻譯成中文就是:將請(qǐng)求的發(fā)送和接收解耦,讓多個(gè)接收對(duì)象都有機(jī)會(huì)處理這個(gè)請(qǐng)求。將這些接收對(duì)象串成一條鏈,并沿著這條鏈傳遞這個(gè)請(qǐng)求,直到鏈上的某個(gè)接收對(duì)象能夠處理它為止。
這么說比較抽象,用更加容易理解的話來進(jìn)一步解讀一下。在責(zé)任鏈模式中,一個(gè)請(qǐng)求過來,會(huì)有多個(gè)處理器(也就是剛剛定義中說的“接收對(duì)象”)依次處理同一個(gè)請(qǐng)求。即請(qǐng)求先經(jīng)過 A 處理器處理,然后再把請(qǐng)求傳遞給 B 處理器,B 處理器處理完后再傳遞給 C 處理器,以此類推,形成一個(gè)鏈條。鏈條上的每個(gè)處理器各自承擔(dān)各自的處理職責(zé),所以叫作責(zé)任鏈模式。

(請(qǐng)雙擊圖片查看)
2.模式demo
2.1 UML
責(zé)任鏈模式(Chain Of Responsibility Design Pattern )的整體結(jié)構(gòu)如下:

(請(qǐng)雙 擊圖片查看 )
2.2 標(biāo)準(zhǔn)demo
我們依據(jù)標(biāo)準(zhǔn)的UML圖,寫出一個(gè)具體的例子(對(duì)應(yīng)UML圖):

(請(qǐng)雙 擊圖片查看 )
首先定義一個(gè)接口 IHandler:
-
type IHandler interface { -
SetNext ( handler IHandler ) -
Handle ( score int ) -
}
然后分別構(gòu)建三個(gè)不同的實(shí)現(xiàn): 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函數(shù):
-
func main () { -
handler1 := & ConcreteHandler1 {} -
handler2 := & ConcreteHandler2 {} -
handler3 := & ConcreteHandler3 {} -
-
handler1 . SetNext ( handler2 ) -
handler2 . SetNext ( handler3 ) -
-
handler1 . Handle ( 10 ) -
-
}
打印結(jié)果為:
-
ConcreteHandler2 處理
2.3 改進(jìn)版demo
通過以上標(biāo)準(zhǔn)例子不難發(fā)現(xiàn): main函數(shù)承接了很多client自身之外的“額外工作”:構(gòu)建和拼接組裝責(zé)任鏈,這不利于后續(xù)client端的使用和擴(kuò)展:一不小心可能責(zé)任鏈拼就接錯(cuò)了或者拼接少節(jié)點(diǎn)了。我們可以對(duì)UML做一個(gè)改進(jìn):增加一個(gè)節(jié)點(diǎn)管理模塊。改進(jìn)圖如下:

(請(qǐng)雙 擊圖片查看 )
對(duì)比上文的uml圖,新增加了一個(gè) ChainHandler結(jié)構(gòu)體用來管理拼接的 Handler,client端無需了解 Handler的業(yè)務(wù), Handler的組合可以使用鏈表,也可以使用數(shù)組(當(dāng)前用了數(shù)組)。具體實(shí)現(xiàn)如下:先定義 Handler接口:
-
type Handler interface { -
Handle ( score int ) -
}
然后分別實(shí)現(xiàn) Handler接口的三個(gè)結(jié)構(gòu)體: 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函數(shù)調(diào)用(client調(diào)用):
-
func main () { -
chain := & ChainHandler {} -
chain . AddHandler (& ConcreteHandlerOne {}) -
chain . AddHandler (& ConcreteHandlerTwo {}) -
chain . AddHandler (& ConcreteHandlerThree {}) -
chain . Handle ( 10 ) -
}
最終的實(shí)現(xiàn)結(jié)構(gòu)圖:

(請(qǐng)雙 擊圖片查看 )
日常工作中出現(xiàn)的責(zé)任鏈模式(Chain Of Responsibility Design Pattern )一般都是以上這種包含 Hanlder管理的模式。
3. 源碼解析
在日常框架和語言基礎(chǔ)庫中,經(jīng)常能夠看到很多場(chǎng)景使用了責(zé)任鏈模式。
3.1 beego過濾器
可以對(duì)比改進(jìn)版demo的uml圖,beego的過濾器就是按照這種模式來設(shè)計(jì)的(當(dāng)前參照的beego版本是2.0)。

(請(qǐng)雙 擊圖片查看 )
3.1.1 client端
調(diào)用端首先是過濾器的注冊(cè):
-
web . InsertFilter ( "/v2/api/*" , web . BeforeRouter , auth . AuthAPIFilter )
然后在 github.com/beego/beego/v2@v2.0.3/server/web/router.go的 ControllerRegister結(jié)構(gòu)體的 serveHttp函數(shù)中
-
if len ( p . filters [ BeforeRouter ]) > 0 && p . execFilter ( ctx , urlPath , BeforeRouter ) { -
goto Admin -
}
以上 p.execFilter(ctx,urlPath,BeforeRouter)處,啟動(dòng)調(diào)用。
3.1.2 Handler接口
Handler接口很簡(jiǎn)單
-
// HandleFunc define how to process the request -
type HandleFunc func ( ctx * beecontext . Context ) -
-
... -
-
type FilterFunc = HandleFunc
3.1.3 Handler接口實(shí)現(xiàn)
接口的實(shí)現(xiàn)擴(kuò)展比較靈活,直接把用戶定義的函數(shù)作為接口的實(shí)現(xiàn)。與client端中的過濾器注冊(cè)聯(lián)動(dòng)。
-
// 過濾器注冊(cè) -
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兩個(gè)結(jié)構(gòu)體中
-
// 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是一個(gè)鏈表,包含用戶自定義的過濾函數(shù); ControllerRegister對(duì) FilterRouter進(jìn)行管理。
3.2 Go源碼http.handler
我們?cè)谑褂肎o構(gòu)建http web服務(wù)器的時(shí)候,使用的http.Handler就是使用的責(zé)任鏈模式。
-
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圖為標(biāo)準(zhǔn),整體的對(duì)照結(jié)構(gòu)圖如下:

(請(qǐng)雙 擊圖片查看 )
3.2.1 client端
整個(gè)模式的啟動(dòng)是隨著http server啟動(dòng)后,接受到請(qǐng)求后的處理開始的。在 net/http/server.go的 serve函數(shù)中
-
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的原理很簡(jiǎn)單,就是for 死循環(huán)等待接收,然后一個(gè)請(qǐng)求過來,就對(duì)應(yīng)的生成一個(gè)單獨(dú)的協(xié)程 goroutine去處理。
3.2.2 Handler接口
Go源碼中對(duì)責(zé)任鏈模式的實(shí)現(xiàn)非常標(biāo)準(zhǔn),Handler接口與設(shè)計(jì)模式中的Handler接口同名,在 net/http/server.go中:
-
type Handler interface { -
ServeHTTP ( ResponseWriter , * Request ) -
}
為了擴(kuò)展方便,在使用過程中并非直接使用,而是中間又加了一層抽象層(相當(dāng)于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接口實(shí)現(xiàn)
與上文中提到的Beego的過濾器類似,Go的Handler設(shè)計(jì)的也非常容易擴(kuò)展,用戶自定義的請(qǐng)求處理函數(shù)Handler都會(huì)變成 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" ) -
} -
// 強(qiáng)制類型轉(zhuǎn)換,轉(zhuǎn)成了實(shí)現(xiàn)了Hanlder的“抽象類”HandlerFunc -
mux . Handle ( pattern , HandlerFunc ( handler )) -
-
}
注意看上文的 HandleFunc中的 mux.Handle(pattern,HandlerFunc(handler))這一行,將用戶自定義的處理函數(shù)強(qiáng)制轉(zhuǎn)換成了上文3.2.2中的 Handler的"抽象類" HandlerFunc類型,進(jìn)而實(shí)現(xiàn)了繼承。
3.2.4 Handler接口的管理類ChainHandler
Go中對(duì)Handler的管理類是在 net/http/server.go文件的 ServeMux結(jié)構(gòu)體和 muxEntry結(jié)構(gòu)體中:
-
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 -
}
其中,用戶自定以的處理函數(shù)都被封裝到了 muxEntry結(jié)構(gòu)體的 Handler中,一個(gè)自定義的函數(shù)對(duì)應(yīng)一個(gè) muxEntry, ServeMux使用hashmap對(duì) muxEntry集合進(jìn)行管理(上文的beego中是使用的鏈表,上文demo中使用了數(shù)組)。當(dāng)web server接收到請(qǐng)求的時(shí)候, ServeMux會(huì)根據(jù)hashmap找到相應(yīng)的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找到相關(guān)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 , "" -
}
在程序運(yùn)行過程中,用戶注冊(cè)自定義的函數(shù)被轉(zhuǎn)化成了 Handler,然后 Handler又結(jié)合用戶自定義的 URL地址被 ServeMux以 URL為Key、 Handler為Value做成hashmap管理起來;等到請(qǐng)求來的時(shí)候, ServeMux就根據(jù)用戶請(qǐng)求的 URL地址,從hashmap中找到具體的 Hanlder來處理請(qǐng)求。
4. 總結(jié)
責(zé)任鏈模式的基本思想就是要處理的請(qǐng)求(通常會(huì)是結(jié)構(gòu)體,然后作為函數(shù)參數(shù));依次經(jīng)過多個(gè)處理對(duì)象處理,這些處理函數(shù)可以動(dòng)態(tài)的添加和刪除,具備很高的靈活性和擴(kuò)展性,通常會(huì)對(duì)這些處理函數(shù)做統(tǒng)一處理,存儲(chǔ)方式一般是通過鏈表、數(shù)組、hash map等存儲(chǔ)結(jié)構(gòu)。
責(zé)任鏈模式的應(yīng)用非常廣泛:
-
業(yè)務(wù)場(chǎng)景:作為敏感詞(涉黃、政治、反動(dòng)等此)過濾的設(shè)計(jì)結(jié)構(gòu)
-
技術(shù)框架:路由、router過濾器、日志log框架等等
推薦閱讀
我為大家整理了一份 從入門到進(jìn)階的Go學(xué)習(xí)資料禮包 ,包含學(xué)習(xí)建議:入門看什么,進(jìn)階看什么。 關(guān)注公眾號(hào) 「polarisxu」,回復(fù) ebook 獲取;還可以回復(fù)「進(jìn)群」,和數(shù)萬 Gopher 交流學(xué)習(xí)。
