実際、多くの人は、責任連鎖モデルが私たちの仕事、特に Web バックエンド エンジニアで頻繁に遭遇するモデルであることを知りません。私たちは仕事で常にそれを使用しています。市販されているものは基本的にこのデザインパターンを基本パターンとして作られています。
1.モード紹介
Chain Of Responsibility デザイン パターンの英語の導入を見てみましょう: 複数のオブジェクトにリクエストを処理する機会を与えることで、リクエストの送信者と受信者の結合を回避します。受信オブジェクトをチェーンし、チェーンに沿ってリクエストを渡します。オブジェクトがそれを処理します。
中国語に翻訳すると、リクエストの送信と受信を分離し、複数の受信オブジェクトがリクエストを処理できるようにします。これらの受信オブジェクトをチェーンに文字列化し、チェーン内の受信オブジェクトの 1 つが処理できるようになるまで、チェーンに沿ってリクエストを渡します。
比較的抽象的なので、わかりやすい言葉で詳しく見てみましょう。責任連鎖モードでは、リクエストが来ると、複数のプロセッサ (つまり、先ほど定義で述べた「受信オブジェクト」) が同じリクエストを順番に処理します。つまり、リクエストは最初にプロセッサ A によって処理され、次にプロセッサ B に渡され、プロセッサ B による処理の後にプロセッサ C に渡され、というようにチェーンを形成します。チェーン内の各プロセッサは独自の処理責任を負うため、責任チェーン モードと呼ばれます。
2. モードデモ
2.1 UML
責任連鎖デザイン パターンの全体的な構造は次のとおりです。
2.2 標準デモ
標準の UML 図に基づいて、具体的な例 (UML 図に対応する) を作成します。
まずインターフェイスを定義しますIHandler
。
type IHandler interface {
SetNext(handler IHandler)
Handle(score int)
}
次に、3 つの異なる実装を個別にビルドします。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 改善されたデモ
通过以上标准例子不难发现: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/[email protected]/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/[email protected]/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
、カスタム関数は1つに対応しておりmuxEntry
、コレクションはハッシュマップ(上記beegoで使用したリンクリスト、上記デモで使用した配列)をServeMux
使用して管理します。 muxEntry
)。Web サーバーはリクエストを受信すると、ServeMux
ハッシュマップに従って対応するハンドラーを見つけて処理します。
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
アドレスと組み合わせてKeyServeMux
とValueとしてハッシュマップとして管理し、リクエストが来た際にそれを元に取得します。ユーザーがリクエストしたアドレス上で、リクエストを処理するためにハッシュマップから特定のアドレスを見つけます。URL
Handler
ServeMux
URL
Hanlder
4. まとめ
Chain of Responsibility パターンの基本的な考え方は、処理されるリクエスト (通常は構造体であり、関数パラメータとして使用されます) であり、複数の処理オブジェクトによって順番に処理され、これらの処理関数は動的に追加および削除できます。高い柔軟性と拡張性 一般に、これらの処理機能は均一に処理され、保存方法はリンク リスト、配列、ハッシュ マップなどの保存構造を使用するのが一般的です。
責任連鎖パターンは広く使用されています。
- ビジネス シナリオ: デリケートな単語 (ポルノ、政治、反動的など) をフィルタリングするための設計構造
- 技術フレームワーク: ルーティング、ルーターフィルター、ログログフレームワークなど。