Go语言设计模式之责任链模式

其实很多人不知道,责任链模式是我们工作中经常遇到的模式,特别是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.1UML

责任链模式(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.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/v2@v2.0.3/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图为标准,整体的结构图如下:

在这里插入图片描述

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
}

其中,用户自定以的处理函数都被封装到了muxEntry结构体的Handler中,一个自定义的函数对应一个muxEntryServeMux使用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地址被ServeMuxURL为Key、Handler为Value做成hashmap管理起来;等到请求来的时候,ServeMux就根据用户请求的URL地址,从hashmap中找到具体的Hanlder来处理请求。

4. 总结

责任链模式的基本思想就是要处理的请求(通常会是结构体,然后作为函数参数);依次经过多个处理对象处理,这些处理函数可以动态的添加和删除,具备很高的灵活性和扩展性,通常会对这些处理函数做统一处理,存储方式一般是通过链表、数组、hash map等存储结构。

责任链模式的应用非常广泛:

  1. 业务场景:作为敏感词(涉黄、政治、反动等此)过滤的设计结构
  2. 技术框架:路由、router过滤器、日志log框架等等
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在Selenium的Python语言中,也可以使用POM(Page Object Model)设计模式来实现页面跳转和滚动浏览。下面是一个示例代码,演示如何在Python中使用POM模式实现页面跳转和滚动浏览: ```python from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.support.ui import Select class HomePage: def __init__(self, driver): self.driver = driver def go_to_login_page(self): login_link = self.driver.find_element_by_link_text("Login") login_link.click() def scroll_to_bottom(self): self.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") ``` 在上面的示例代码中,我们定义了一个HomePage类,其中包含了两个方法:go_to_login_page()用于跳转到登录页面,scroll_to_bottom()用于滚动到页面底部。这些方法都是基于Selenium WebDriver实现的,可以与其他测试代码一起使用,以实现自动化测试的目的。 在这个示例中,我们使用了Selenium WebDriver提供的方法来找到页面上的元素(如链接)并与之交互。我们也使用了execute_script()方法来执行JavaScript代码以实现页面滚动。 使用POM模式可以使测试代码更加模块化和可维护,从而提高测试的效率和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菜刚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值