• golang设计模式——职责链模式


    职责链模式

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fokJYhdk-1660574053651)(C:/Users/86158/AppData/Roaming/Typora/typora-user-images/image-20220815221234932.png)]

    职责链将处理模块串联成链,请求沿着链条被处理,提供了很好的扩展性,而且能够去掉if-else。

    职责链模式(Chain of Responsibility):使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UA5ElcP4-1660574053652)(C:/Users/86158/AppData/Roaming/Typora/typora-user-images/image-20220815221610616.png)]

    分析

    职责链模式的定义已经将模式说的比较清楚了,一个请求需要被多个对象进行处理,可以将处理对象连成一条链,挨个处理请求。

    连成链的方式比较多样,可以用UML中展示的那样,一个处理对象使用SetSuccessor引用下一个处理对象。也可以使用array或者list存储所有处理对象,使用循环方式遍历。

    对于第二种方式,是否感觉有些像观察者两者具体实现、目的都差不多,主要区别在观察者模式中的处理对象功能可能完全不相似,而且观察者模式主要负责将信息传递给处理对象即可。职责链模式的处理对象功能一般相似,另外职责链模式也关注请求是否正确被处理。

    另外,定义里说”直到有一个对象处理它“也不太准确,有多少对象可以处理请求看具体需求,极端情况下每一个对象都可以处理请求。

    职责链模式的核心在于将处理对象整理成链路。

    应用场景

    如果请求被多个对象进行处理,就可以用职责链模式。具体业务的像敏感词脱敏,框架中的过滤器、拦截器等。

    总体感觉框架中使用的比较多一些,研发人员能够快速扩展出自己的过滤器和拦截器

    代码实现

    我们仿照Gin,实现Gin的全局中间件功能。

    package main
    
    import "fmt"
    
    var status int8 = 0
    
    type HandlerFunc func()
    
    type HandlersChain []HandlerFunc
    
    /**
     * @Description:
     */
    type RouterGroup struct {
       Handlers HandlersChain
       index    int8
    }
    
    /**
     * @Description: 添加中间件,将其组成链式
     * @receiver group
     * @param middleware
     */
    func (group *RouterGroup) Use(middleware ...HandlerFunc) {
       group.Handlers = append(group.Handlers, middleware...)
    }
    
    /**
     * @Description: 链顺序执行
     * @receiver group
     */
    func (group *RouterGroup) Next() {
       for group.index < int8(len(group.Handlers)) {
          group.Handlers[group.index]()
          group.index++
       }
    }
    
    /**
     * @Description: 中间件
     */
    func middleware1() {
       fmt.Println("全局中间件1执行完毕")
    }
    
    /**
     * @Description: 中间件
     */
    func middleware2() {
       fmt.Println("全局中间件2执行失败")
       status = 1
    }
    
    func main() {
       r := &RouterGroup{}
       //添加中间件
       r.Use(middleware1, middleware2)
       //运行中间件
       r.Next()
       //状态检查
       if status == 1 {
          fmt.Println("中间件检查失败,请重试")
          return
       }
       //执行后续流程
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    输出:

    ➜ myproject go run main.go

    全局中间件1执行完毕

    全局中间件2执行失败

    中间件检查失败,请重试

    这是一个简版的中间件执行过程,我将Gin中的Context和RouterGroup合并了。虽然比起真正的执行流程缺乏很多内容,但是核心操作是一致的。

    实例

    首先我们看一下一个简单的实现模板,然后我们再看看实际上我们常用 web 框架 gin 当中是如何处理请求的

    代码

    // Package chain 职责链模式
    // 🌰 假设我们现在有个校园论坛,由于社区规章制度、广告、法律法规的原因需要对用户的发言进行敏感词过滤
    //    如果被判定为敏感词,那么这篇帖子将会被封禁
    package chain
    
    // SensitiveWordFilter 敏感词过滤器,判定是否是敏感词
    type SensitiveWordFilter interface {
    	Filter(content string) bool
    }
    
    // SensitiveWordFilterChain 职责链
    type SensitiveWordFilterChain struct {
    	filters []SensitiveWordFilter
    }
    
    // AddFilter 添加一个过滤器
    func (c *SensitiveWordFilterChain) AddFilter(filter SensitiveWordFilter) {
    	c.filters = append(c.filters, filter)
    }
    
    // Filter 执行过滤
    func (c *SensitiveWordFilterChain) Filter(content string) bool {
    	for _, filter := range c.filters {
    		// 如果发现敏感直接返回结果
    		if filter.Filter(content) {
    			return true
    		}
    	}
    	return false
    }
    
    // AdSensitiveWordFilter 广告
    type AdSensitiveWordFilter struct{}
    
    // Filter 实现过滤算法
    func (f *AdSensitiveWordFilter) Filter(content string) bool {
    	// TODO: 实现算法
    	return false
    }
    
    // PoliticalWordFilter 政治敏感
    type PoliticalWordFilter struct{}
    
    // Filter 实现过滤算法
    func (f *PoliticalWordFilter) Filter(content string) bool {
    	// TODO: 实现算法
    	return true
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    单元测试

    package chain
    
    import (
    	"testing"
    
    	"github.com/stretchr/testify/assert"
    )
    
    func TestSensitiveWordFilterChain_Filter(t *testing.T) {
    	chain := &SensitiveWordFilterChain{}
    	chain.AddFilter(&AdSensitiveWordFilter{})
    	assert.Equal(t, false, chain.Filter("test"))
    
    	chain.AddFilter(&PoliticalWordFilter{})
    	assert.Equal(t, true, chain.Filter("test"))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    Gin 的中间件实现

    我们直接看一下 gin Context 的实现,其中 Next() 方法就是主要的执行方法,这里其实就是我们最上面说到的职责链模式的变体,因为它会在每一个处理函数中进行处理,而不是第一个接收到就停止了

    type Context struct {
        // ...
    
        // handlers 是一个包含执行函数的数组
        // type HandlersChain []HandlerFunc
    	handlers HandlersChain
        // index 表示当前执行到哪个位置了
    	index    int8
    
        // ...
    }
    
    // Next 会按照顺序将一个个中间件执行完毕
    // 并且 Next 也可以在中间件中进行调用,达到请求前以及请求后的处理
    // Next should be used only inside middleware.
    // It executes the pending handlers in the chain inside the calling handler.
    // See example in GitHub.
    func (c *Context) Next() {
    	c.index++
    	for c.index < int8(len(c.handlers)) {
    		c.handlers[c.index](c)
    		c.index++
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    总结

    通过Gin中间件的例子,可以很好证明职责链的扩展性。简单使用Use增加自己创建的中间件,每一个请求都会被新增的中间件所处理。所以开发者可以方便的增加鉴权、限流、脱敏、拦截等操作。这就是所谓的优雅吧。

  • 相关阅读:
    100 行 C++ 代码,教你快速实现视频画面动态分割!
    【Python画图-驯化01】一文叫你搭建python画图最优环境配置
    痞子衡嵌入式:MCUXpresso IDE下高度灵活的FreeMarker链接文件模板机制
    Vue3 父组件调用子组件方法($refs 在setup()、<script setup> 中使用)
    MYSQL之DCL
    cors基础,响应头设置
    算法-计算机基础知识
    LeetCode 121. 买卖股票的最佳时机
    Vue根据屏幕分辨率计算div可以显示的数量,dom渲染在v-if之后造成的复杂处理
    【yolov5】原理详解
  • 原文地址:https://blog.csdn.net/qq_53267860/article/details/126356163