Responsibility chain mode of Go language design mode

In fact, many people don't know that the chain of responsibility model is a model we often encounter in our work, especially web back-end engineers. We use it all the time in our work: because most of the filters of web frameworks on the market are basically It is built based on this design pattern as the basic pattern.

1. Mode introduction

Let's take a look at the English introduction of 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.

Translated into Chinese is: decouple the sending and receiving of the request, so that multiple receiving objects have the opportunity to process the request. String these receiving objects into a chain, and pass the request along the chain until one of the receiving objects in the chain can handle it.

This is relatively abstract, so let's take a closer look at it in easier-to-understand words. In the chain of responsibility mode, when a request comes, there will be multiple processors (that is, the "receiving object" just mentioned in the definition) to process the same request in sequence. That is, the request is first processed by processor A, and then passed to processor B, and after processing by processor B, it is passed to processor C, and so on, forming a chain. Each processor in the chain assumes its own processing responsibilities, so it is called the responsibility chain mode.

hanlder_design1.drawio.png

2. Mode demo

2.1 UML

The overall structure of the Chain Of Responsibility Design Pattern is as follows:

hanlder_chain_design1.drawio.png

2.2 Standard demo

Based on the standard UML diagram, we write a specific example (corresponding to the UML diagram):

demo_chain.drawio.png

First define an interface IHandler:

type IHandler interface {
	SetNext(handler IHandler)
	Handle(score int)
}

Then build three different implementations separately: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
}

And finally mainthe function:

func main() {
	handler1 := &ConcreteHandler1{}
	handler2 := &ConcreteHandler2{}
	handler3 := &ConcreteHandler3{}

	handler1.SetNext(handler2)
	handler2.SetNext(handler3)

	handler1.Handle(10)

}

The printed result is:

ConcreteHandler2 处理

2.3 Improved demo

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

chain_design_uml.drawio.png

对比上文的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_demo_chain.drawio.png

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

3. 源码解析

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

3.1 beego过滤器

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

beego_chain.drawer.png

3.1.1 client端

调用端首先是过滤器的注册:

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

然后在github.com/beego/beego/[email protected]/server/web/router.goControllerRegister结构体的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的中的 FilterRouterControllerRegister两个结构体中

// 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是一个链表,包含用户自定义的过滤函数;ControllerRegisterFilterRouter进行管理。

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图为标准,整体的对照结构图如下:

go_hanlder_demo.drawio.png

3.2.1 client端

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

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
}

Among them, the user-defined processing functions are all encapsulated into muxEntrythe structure Handler, and one custom function corresponds to one muxEntry, and the collection is managed ServeMuxby using hashmap muxEntry(the linked list used in the above beego, and the array used in the above demo ). When the web server receives a request, ServeMuxit will find the corresponding handler according to the hashmap and process it.

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, ""
}

During the running of the program, the user-defined function registered by the user is transformed Handler, and Handlerthen combined with the user-defined URLaddress, it is managed ServeMuxasURL a Key and Handlera value as a hashmap; when the request comes, it is based on the address ServeMuxrequested by the user. URL, Find the specific one from the hashmap Hanlderto process the request.

4. Summary

The basic idea of ​​the Chain of Responsibility pattern is the request to be processed (usually a structure, and then used as a function parameter); processed by multiple processing objects in turn, these processing functions can be dynamically added and deleted, with high flexibility and expansion Generally, these processing functions are processed uniformly, and the storage method is generally through storage structures such as linked lists, arrays, and hash maps.

The Chain of Responsibility pattern is widely used:

  1. Business scenario: design structure for filtering sensitive words (pornographic, political, reactionary, etc.)
  2. Technical framework: routing, router filter, log log framework, etc.

Guess you like

Origin juejin.im/post/7261119531892031549