【Gin】架构的精妙编织:Gin框架中组合模式的革新实践与技术深度解析(下)

【Gin】架构的精妙编织:Gin框架中组合模式的革新实践与技术深度解析(下)

大家好 我是寸铁👊

【Gin】架构的精妙编织:Gin框架中组合模式的革新实践与技术深度解析(下)✨

喜欢的小伙伴可以点点关注 💝


前言

本次文章分为上下两部分,上部分为对理论的介绍,下部分为具体的底层代码深度剖析和编程实践,感兴趣的伙伴不要错过哦~

在现代软件工程中,架构设计的精妙编织是构建稳健和高效系统的关键。组合模式作为一种经典的设计模式,通过将对象组合成树形结构以表示"部分-整体"的层次结构,已被广泛应用于各种领域的软件开发中。特别是在Gin框架这样的轻量级、高性能Web框架中,组合模式展现了其在管理复杂性和促进代码复用方面的卓越价值。本文将深入探讨组合模式在Gin框架中的革新实践和技术深度,帮助开发者全面理解如何利用组合模式优化和增强其应用程序的架构设计。

组合模式通过递归结构和多态性质,使得单个对象和组合对象在使用上具有一致性,从而使得整个系统的设计更加灵活和可扩展。在Gin框架中,组合模式可以被应用于路由结构、中间件组合以及请求处理管道的设计中,使得开发者能够更加自如地处理复杂的业务逻辑和请求处理流程。本文旨在为开发者提供深入的技术见解和实用的应用指南,帮助他们有效地运用组合模式,打造出更加稳健和可维护的Gin框架应用程序。


关键的类图和时序图

(1) 类图
Component:是组合中所有对象的基类,定义了组合中对象和组合对象的共有操作。拥有一个操作方法 operation(),可以在具体的LeafComposite中被实现。
Path(叶节点):表示组合中的叶子节点对象,它没有子节点。继承自Component类,实现了operation()方法,表示基本的操作。
PathComposite(复合对象):表示组合中的复合对象,可以包含其他PathComposite对象。继承自Component类,包含了管理子组件的方法,如add(), remove(), getChild()等,同时也实现了operation()方法以处理组合对象的操作。

图58 组合模式的类图

由上图58可得:

在Gin框架中,组合模式主要体现在访问路由上,叶子节点为Path路径,组合对象为PathComposite

先定义Component类,即组合中所有对象的基类,定义了组合中对象和组合对象的共有操作。拥有一个操作方法 operation(),可以在具体的PathComposite中被实现。

再编写Path(叶节点),即表示组合中的叶子节点对象,它没有子节点。继承自Component类,实现了operation()方法,表示基本的操作。

然后编写PathComposite(复合对象)即表示组合中的复合对象,可以包含其他PathComposite对象。继承自Component类,包含了真正实现管理子组件的方法,如add(), remove(), getChild()等,同时也实现了operation()方法以处理组合对象的操作。


(2) 时序图

图59 组合模式时序图

由上图59可得:

组合模式时序图说明:

客户端先创建具体的Composite组合对象,继承自Component类。

再创建叶子对象Path,继承自Component类。

接着调用Compositejoin方法将叶子对象Path进行拼接得到可访问的路由对象router

再逐步将拼接好的路由对象router返回给客户端Client进行调用和其他操作operation()


主程序的流程

图60 组合模式主程序流程图

由上图60可得:程序一开始,客户端先创建路由组合对象Composite ,接着创建叶子对象Path ,然后调用组合对象Compositejoin方法将Path进行拼接,再逐步返回拼接好后的路由对象给客户端,客户端获得路由对象后,进行业务处理,业务处理完毕后,程序结束。


程序模块之间的调用关系

图61 组合模式程序调用图

由上图61可得:

Gin 框架的组合模式,涉及的角色如下:
路由引擎 (Router Engine)
router := gin.Default() 创建了 Gin 框架的路由引擎实例。该引擎负责注册路由、处理 HTTP 请求,并根据请求的路径和方法分发到相应的处理函数。


(1) 路由组 (Router Group):

api := router.Group("/api") 和 admin := router.Group("/admin") 定义了两个路由组。路由组是将相关联的路由进行组织和管理的机制。在这段代码中,/api 和 /admin 分别是两个不同的路径前缀,用于区分不同的功能模块或权限要求。


(2) 路由处理函数 (Route Handlers):

在路由组中使用 api.GET("/users", ...)、api.GET("/admins", ...) 和 admin.POST("/login", ...) 注册了三个具体的路由处理函数。这些处理函数定义了当匹配到特定 HTTP 方法和路径时应执行的操作。例如,GET /api/users 返回一个 JSON 响应,而 POST /admin/login 处理用户登录操作。


(3) 中间件 (Middleware):

虽然在提供的代码中未显式使用中间件,但是 Gin 框架支持在路由组或全局中应用中间件。中间件可以用来处理认证、日志记录、错误处理等通用功能,以增强路由的功能性和可复用性。


(4) Context 对象 (gin.Context):
c *gin.Context 是在每个路由处理函数中作为参数传递的上下文对象。它包含了关于 HTTP 请求的所有信息,如请求头、请求体、路径参数等,并且提供了用于设置响应的方法。

可以将访问路由整理成一棵路由组合树如下图62:

图62 路由组合树

下面是对上图61各层次调用关系的描述:

客户端调用router.Group定义路由组,要访问的路由类型为String类型,用于拼接路由的路径。进一步在router.Group方法中的RouterGroup结构体中调用basePath: group.calculateAbsolutePath(relativePath)计算绝对路径方法,在基础路径的基础上对相对路径进行拼接。group.calculateAbsolutePath方法中再调用joinPaths(group.basePath, relativePath)方法将相对路径进行拼接返回拼接好的路由组基础路由,在将组装好的路由组对象router.Group返回给客户端的api对象。
api对象拿到router.Group对象后,调用GET方法,设置要访问的路由组的子路由路径,实现在路由组的基础上,添加子路由路径到路由组形成部分-整体的树形结构,GET方法将要拼接的路径转发给里面的group.handle(http.MethodGet, relativePath, handlers)方法,再调用group.calculateAbsolutePath(relativePath)方法计算每个子路由访问的绝对路径,方法内部调用joinPaths(group.basePath, relativePath)计算出最后的绝对路径。最后将要访问的子路由对象返回给客户端。


在上图的基础上,下面对各个模块的代码进行深入剖析:

图63 组合模式Group方法声明

代码位置:routergroup.go的72-78行

72行:Group() 方法是 RouterGroup 结构体的一个方法。它接受一个相对路径relativePath和一个或多个HandlerFunc类型的中间件函数作为参数,并返回一个指向 RouterGroup 结构体的指针。在方法内部,创建了一个新的 RouterGroup 对象,并用一个结构体字面量初始化该对象。

75行:basePath: 使用group.calculateAbsolutePath(relativePath)方法计算相对路径 relativePath 的绝对路径,赋值给 basePath。这是为了构建新路由组的完整路径。


calculateAbsolutePath方法的代码如下,将构建路径的请求转发给joinPaths方法:

图64 组合模式计算绝对路径方法代码

代码位置:routergroup.go的250-252行

calculateAbsolutePath() 方法是RouterGroup结构体的一个方法。它接受一个 relativePath 参数,该参数表示相对路径,返回一个字符串类型的绝对路径。在方法内部,调用了joinPaths()函数,将 group.basePathrelativePath作为参数传递给它,并返回其计算结果。


joinPaths的代码如下:

图65 组合模式的joinPaths方法代码

代码位置:routergroup.go的128-137行

joinPaths() 是一个函数,接受两个字符串类型的参数absolutePathrelativePath,分别表示绝对路径和相对路径。如果relativePath是空字符串,直接返回 absolutePath。这种情况下,无需进行路径连接,直接返回当前的绝对路径。使用标准库中的path.Join()函数将 absolutePath relativePath 进行路径连接,生成最终的路径 finalPath。 检查relativePath的最后一个字符是否为 '/',并且 finalPath 的最后一个字符不是 '/'。如果满足这个条件,将finalPath的末尾添加 '/',以确保路径的一致性和正确性。返回经过处理后的最终路径 finalPath

在得到路由组对象的基础路径后,下面要对子路由的路径进行组合,先调用GET方法对要组合的路径进行转发,转发到group.handle()方法进行处理:

代码位置:routergroup.go的116-118行

图66 GET方法代码

GET() 方法是RouterGroup结构体的一个方法。它接受一个 relativePath 参数作为路径,以及一个或多个 HandlerFunc 类型的处理函数作为中间件,返回一个实现了IRoutes接口的对象。调用 group.handle() 方法,将 HTTP 方法、路径和中间件函数传递给该方法处理,进而配置 GET 请求的路由和处理流程。

转发到handle方法中,调用group.calculateAbsolutePath (relativePath)方法对路径进行拼接。

图67 组合模式的handle方法代码

代码位置:routergroup.go的86-87行


图68 组合模式计算路由代码

代码位置:routergroup.go的250-252行

calculateAbsolutePath() 方法是RouterGroup结构体的一个方法。它接受一个relativePath参数,该参数表示相对路径,返回一个字符串类型的绝对路径。在方法内部,调用了joinPaths()函数,将 group.basePath relativePath 作为参数传递给它,并返回最后拼接的子路由路径。


组合模式案例及调试分析

组合模式主要用于处理树形结构的问题,其中包含两种基本对象类型:叶子对象和组合对象。在编写组合模式案例中,我们可以将路由组和路由视作组合模式中的组合对象和叶子对象。

图113 定义路由组代码

组合对象 (RouteGroup):

定义:RouteGroup类型表示路由的组合对象,它可以包含自己的访问路径Path、子路由 (Route 对象) 和子路由组 (RouteGroup 对象)。

角色: 在组合模式中,RouteGroup 充当组合对象角色。

职责:

维护子对象列表 (Routes 和 SubGroups)。

提供方法 (AddRoute 和 AddSubGroup),用于添加子对象。

可以被递归地操作,因为它可以包含其他RouteGroup对象作为子组。


图114 定义单个路由Route代码

叶子对象 (Route):

定义: Route 类型表示单个的路由对象,包含了请求路径PathHTTP 方法Method和处理函数Handler。

角色: 在组合模式中,Route 充当叶子对象角色。

职责:

表示组合对象的最小单位,不能包含其他对象。包含具体的路由信息和对应的处理逻辑。
AddRoute方法向路由组中添加子路由

图115 AddRoute代码

AddRoute 是一个方法,它绑定到 RouteGroup 结构体上。参数包括 path(路由路径)method(HTTP 方法) handler(处理函数)。在 AddRoute 方法内部,首先创建了一个 Route 对象 routeroute 是指向 Route 结构体的指针。Path 被赋值为传入的 path 参数,表示路由的路径。Method 被赋值为传入的 method 参数,表示路由的 HTTP 方法。Handler 被赋值为传入的 handler 参数,即处理该路由的函数。将路由route添加到 Routes 切片rg.Routes。

主程序 (main 函数):

图116 main方法代码

定义: main 函数作为程序的入口点。

角色: 在组合模式中不直接对应特定角色,但是通过创建和管理 RouteGroupRoute 对象来实现组合模式中的结构。

职责:

创建顶级的 RouteGroup 对象 (api)。

使用AddRoute方法向 api 路由组添加GET路由。

使用 api.AddRoute 方法向 api 路由组添加一个 GET 方法的路由,路径为/users。匿名函数作为处理函数,当请求 /api/users 时,控制台输出发送响应信息完毕,组合对象成功,组合模式测试成功!并返回 JSON 格式的数据。

类似地,向 api 路由组添加另一个 POST方法的路由,路径为 /admins

当请求/api/admins时,控制台输出发送响应信息完毕,组合对象成功,组合模式测试成功!返回 JSON 格式的数据。

调用router.Run(":8080")方法启动 Gin 路由引擎,监听本地的 8080 端口,开始接受和处理来自客户端的 HTTP 请求。

使用initializeRoutes函数将路由组注册到 Gin 路由引擎中。

initializeRoutes 函数如下:

图117 initializeRoutes函数代码

initializeRoutes 的函数,接收两个参数:
router:Gin 框架的路由引擎,用于注册路由。
group:自定义的 RouteGroup 结构体指针,包含了一组相关的路由信息。

从传入的 group 结构体中获取 Path 属性,表示当前路由组的路径。

使用 range 遍历 group 中的 Routes 数组,

对于每个路由条目 route,根据其 Method 字段的值进行注册:

如果是 "GET" 方法,调用router.GET()方法注册 GET 请求处理器,路径为 groupPath + route.Path,处理函数为 route.Handler

如果是 "POST" 方法,调用 router.POST() 方法注册 POST 请求处理器,路径同样为 groupPath + route.Path,处理函数为 route.Handler。

可以根据实际需求继续添加其他 HTTP 方法的注册,例如 PUTDELETE 等。

遍历当前 group 的子路由组 SubGroups,对每个子路由组递归调用 initializeRoutes 函数。

这确保了所有子路由组中的路由也会被注册到 router 中,实现了路由的嵌套和层级管理。

调试分析:

启动测试案例的服务端,完成对象的组合,形成访问路由树,并等待客户端请求的发送。运行测试组合模式案例成功!

图118 运行测试组合模式案例成功

使用API测试工具APIfox,向服务器监听的端口发送GET请求,并得到服务端发送的响应信息,调试组合模式案例成功!如下图119:

图119 Apifox测试GET请求

使用API测试工具APIfox,向服务器监听的端口发送POST请求,并得到服务端发送的响应信息,调试组合模式案例成功!如下图120:

图120 Apifox测试POST请求


组合模式测试结果

使用API测试工具APIfox,向服务器监听的端口发送GET请求,并得到服务端发送的响应信息,说明可以正常的访问路由,路由组合成功!测试组合模式案例成功!

图142 Apifox发起GET请求

使用API测试工具APIfox,向服务器监听的端口发送POST请求,并得到服务端发送的响应信息,说明可以正常的访问路由,路由组合成功!测试组合模式案例成功!

图143 Apifox发起POST请求

再观察服务端的监控信息,预估是否与测试案例编写的一致。

服务端在监听客户端发送完请求后,应该在控制台中输出如下的语句:发送响应信息完毕,组合对象成功,组合模式测试成功!

图144 路由对象添加路由

发现控制台输出的信息确实是:发送响应信息完毕,组合对象成功,组合模式案例测试成功!

这表明路由对象组合成功,可以正常地访问组合的路由,组合模式测试成功!

图145 服务端后台监控信息测试成功


结语

通过本文的深入探讨,我们详细分析了组合模式在Gin框架中的应用场景和实际案例。组合模式不仅能够优雅地解决复杂系统中的结构化问题,还能够提升系统的灵活性和可扩展性,使开发者能够更加高效地应对不断变化的业务需求和技术挑战。在实际项目中,合理运用组合模式能够有效地简化系统的设计与维护,降低代码的复杂度,从而为Gin框架应用的长期发展提供坚实的技术基础。希望本文能够为广大开发者提供有益的参考和实用的指导,帮助他们在实际应用中充分发挥组合模式的优势,构建出更加强大和灵活的软件系统。


看到这里的小伙伴,恭喜你又掌握了一个技能👊

希望大家能取得胜利,坚持就是胜利💪

我是寸铁!我们下期再见💕


往期好文💕

保姆级教程

【保姆级教程】Windows11下go-zero的etcd安装与初步使用

【保姆级教程】Windows11安装go-zero代码生成工具goctl、protoc、go-zero

【Go-Zero】手把手带你在goland中创建api文件并设置高亮


报错解决

【Go-Zero】Error: user.api 27:9 syntax error: expected ':' | 'IDENT' | 'INT', got '(' 报错解决方案及api路由注意事项

【Go-Zero】Error: only one service expected goctl一键转换生成rpc服务错误解决方案

【Go-Zero】【error】 failed to initialize database, got error Error 1045 (28000):报错解决方案

【Go-Zero】Error 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)报错解决方案

【Go-Zero】type mismatch for field "Auth.AccessSecret", expect "string", actual "number"报错解决方案

【Go-Zero】Error: user.api 30:2 syntax error: expected ')' | 'KEY', got 'IDENT'报错解决方案

【Go-Zero】Windows启动rpc服务报错panic:context deadline exceeded解决方案


Go面试向

【Go面试向】defer与time.sleep初探

【Go面试向】defer与return的执行顺序初探

【Go面试向】Go程序的执行顺序

【Go面试向】rune和byte类型的认识与使用

【Go面试向】实现map稳定的有序遍历的方式

相关推荐
江_小_白35 分钟前
自动驾驶之激光雷达
人工智能·机器学习·自动驾驶
yusaisai大鱼2 小时前
TensorFlow如何调用GPU?
人工智能·tensorflow
Estar.Lee2 小时前
查手机号归属地免费API接口教程
android·网络·后端·网络协议·tcp/ip·oneapi
2401_857610034 小时前
SpringBoot社团管理:安全与维护
spring boot·后端·安全
凌冰_4 小时前
IDEA2023 SpringBoot整合MyBatis(三)
spring boot·后端·mybatis
珠海新立电子科技有限公司4 小时前
FPC柔性线路板与智能生活的融合
人工智能·生活·制造
码农飞飞4 小时前
深入理解Rust的模式匹配
开发语言·后端·rust·模式匹配·解构·结构体和枚举
一个小坑货4 小时前
Rust 的简介
开发语言·后端·rust
IT古董5 小时前
【机器学习】机器学习中用到的高等数学知识-8. 图论 (Graph Theory)
人工智能·机器学习·图论
曼城周杰伦5 小时前
自然语言处理:第六十三章 阿里Qwen2 & 2.5系列
人工智能·阿里云·语言模型·自然语言处理·chatgpt·nlp·gpt-3