我们先来看一下责任链模式(ChainOfResponsibilityDesignPattern)的英文介绍:Avoidcouplingthesenderofarequesttoitsreceiverbygivingmorethanoneobjectachancetohandletherequest.Chainthereceivingobjectsandpasstherequestalongthechainuntilanobjecthandlesit.
翻译成中文就是:将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止。
这么说比较抽象,用更加容易理解的话来进一步解读一下。在责任链模式中,一个请求过来,会有多个处理器(也就是刚刚定义中说的“接收对象”)依次处理同一个请求。即请求先经过A处理器处理,然后再把请求传递给B处理器,B处理器处理完后再传递给C处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职责,所以叫作责任链模式。
责任链模式(ChainOfResponsibilityDesignPattern)的整体结构如下:
我们依据标准的UML图,写出一个具体的例子(对应UML图):
首先定义一个接口IHandler:
typeIHandlerinterface{ SetNext(handlerIHandler) Handle(scoreint)}然后分别构建三个不同的实现:ConcreteHandler1
typeConcreteHandler2struct{ NextIHandler}func(c*ConcreteHandler2)Handle(scoreint){ ifscore>0{ fmt.Println("ConcreteHandler2处理") return } ifc.Next!=nil{ c.Next.Handle(score) } return}func(c*ConcreteHandler2)SetNext(handlerIHandler){ c.Next=handler}ConcreteHandler3
typeConcreteHandler3struct{ NextIHandler}func(c*ConcreteHandler3)Handle(scoreint){ ifscore==0{ fmt.Println("ConcreteHandler3处理") return } ifc.Next!=nil{ c.Next.Handle(score) } return}func(c*ConcreteHandler3)SetNext(handlerIHandler){ c.Next=handler}最后是main函数:
ConcreteHandler2处理2.3改进版demo通过以上标准例子不难发现:main函数承接了很多client自身之外的“额外工作”:构建和拼接组装责任链,这不利于后续client端的使用和扩展:一不小心可能责任链拼就接错了或者拼接少节点了。我们可以对UML做一个改进:增加一个节点管理模块。改进图如下:
对比上文的uml图,新增加了一个ChainHandler结构体用来管理拼接的Handler,client端无需了解Handler的业务,Handler的组合可以使用链表,也可以使用数组(当前用了数组)。具体实现如下:先定义Handler接口:
typeHandlerinterface{ Handle(scoreint)}然后分别实现Handler接口的三个结构体:ConcreteHandlerOne
typeConcreteHandlerTwostruct{ Handler}func(c*ConcreteHandlerTwo)Handle(scoreint){ ifscore>0{ fmt.Println("ConcreteHandler2处理") return }}ConcreteHandlerThree
typeConcreteHandlerThreestruct{ Handler}func(c*ConcreteHandlerThree)Handle(scoreint){ ifscore==0{ fmt.Println("ConcreteHandler3处理") return }}main函数调用(client调用):
日常工作中出现的责任链模式(ChainOfResponsibilityDesignPattern)一般都是以上这种包含Hanlder管理的模式。
在日常框架和语言基础库中,经常能够看到很多场景使用了责任链模式。
可以对比改进版demo的uml图,beego的过滤器就是按照这种模式来设计的(当前参照的beego版本是2.0)。
调用端首先是过滤器的注册:
web.InsertFilter("/v2/api/*",web.BeforeRouter,auth.AuthAPIFilter)然后在github.com/beego/beego/v2@v2.0.3/server/web/router.go的ControllerRegister结构体的serveHttp函数中
Handler接口很简单
//HandleFuncdefinehowtoprocesstherequesttypeHandleFuncfunc(ctx*beecontext.Context) ... typeFilterFunc=HandleFunc3.1.3Handler接口实现接口的实现扩展比较灵活,直接把用户定义的函数作为接口的实现。与client端中的过滤器注册联动。
//过滤器注册web.InsertFilter("/v2/api/*",web.BeforeRouter,auth.AuthAPIFilter)//自定义过滤器varAuthAPIFilter=func(ctx*context.Context){ isAccess:=validateAccess(ctx) if!isAccess{ res,_:=json.Marshal(r) ctx.WriteString(string(res)) //ctx.Redirect(401,"/401") }}3.1.4Handler管理Handler的管理模块是在github.com/beego/beego/v2@v2.0.3/server/web/router.go的中的FilterRouter和ControllerRegister两个结构体中
typeHandlerinterface{ ServeHTTP(ResponseWriter,*Request)}为了扩展方便,在使用过程中并非直接使用,而是中间又加了一层抽象层(相当于Java中的抽象类了,Go中没有抽象类)
//TheHandlerFunctypeisanadaptertoallowtheuseof//ordinaryfunctionsasHTTPhandlers.Iffisafunction//withtheappropriatesignature,HandlerFunc(f)isa//Handlerthatcallsf.typeHandlerFuncfunc(ResponseWriter,*Request)//ServeHTTPcallsf(w,r).func(fHandlerFunc)ServeHTTP(wResponseWriter,r*Request){ f(w,r)}3.2.3Handler接口实现与上文中提到的Beego的过滤器类似,Go的Handler设计的也非常容易扩展,用户自定义的请求处理函数Handler都会变成Handler的子类。
typeServeMuxstruct{ musync.RWMutex mmap[string]muxEntry es[]muxEntry//sliceofentriessortedfromlongesttoshortest. hostsbool//whetheranypatternscontainhostnames}typemuxEntrystruct{ hHandler patternstring}其中,用户自定以的处理函数都被封装到了muxEntry结构体的Handler中,一个自定义的函数对应一个muxEntry,ServeMux使用hashmap对muxEntry集合进行管理(上文的beego中是使用的链表,上文demo中使用了数组)。当webserver接收到请求的时候,ServeMux会根据hashmap找到相应的handler然后处理。
责任链模式的基本思想就是要处理的请求(通常会是结构体,然后作为函数参数);依次经过多个处理对象处理,这些处理函数可以动态的添加和删除,具备很高的灵活性和扩展性,通常会对这些处理函数做统一处理,存储方式一般是通过链表、数组、hashmap等存储结构。