【Gin】架构的精妙编织:Gin框架中组合模式的革新实践与技术深度解析(下)
大家好 我是寸铁👊
【Gin】架构的精妙编织:Gin框架中组合模式的革新实践与技术深度解析(下)✨
喜欢的小伙伴可以点点关注 💝
前言
本次文章分为上下两部分,上部分为对理论的介绍,下部分为具体的底层代码深度剖析和编程实践,感兴趣的伙伴不要错过哦~
在现代软件工程中,架构设计的精妙编织是构建稳健和高效系统的关键。组合模式作为一种经典的设计模式,通过将对象组合成树形结构以表示"部分-整体"的层次结构,已被广泛应用于各种领域的软件开发中。特别是在Gin框架这样的轻量级、高性能Web框架中,组合模式展现了其在管理复杂性和促进代码复用方面的卓越价值。本文将深入探讨组合模式在Gin框架中的革新实践和技术深度,帮助开发者全面理解如何利用组合模式优化和增强其应用程序的架构设计。
组合模式通过递归结构和多态性质,使得单个对象和组合对象在使用上具有一致性,从而使得整个系统的设计更加灵活和可扩展。在Gin框架中,组合模式可以被应用于路由结构、中间件组合以及请求处理管道的设计中,使得开发者能够更加自如地处理复杂的业务逻辑和请求处理流程。本文旨在为开发者提供深入的技术见解和实用的应用指南,帮助他们有效地运用组合模式,打造出更加稳健和可维护的Gin框架应用程序。
关键的类图和时序图
(1) 类图
Component
:是组合中所有对象的基类,定义了组合中对象和组合对象的共有操作。拥有一个操作方法 operation()
,可以在具体的Leaf
或Composite
中被实现。
Path
(叶节点):表示组合中的叶子节点对象,它没有子节点。继承自Component
类,实现了operation()
方法,表示基本的操作。
PathComposite
(复合对象):表示组合中的复合对象,可以包含其他Path
或Composite
对象。继承自Component类,包含了管理子组件的方法,如add()
, remove()
, getChild()
等,同时也实现了operation()
方法以处理组合对象的操作。
图58 组合模式的类图
由上图58可得:
在Gin框架中,组合模式主要体现在访问路由上,叶子节点为Path
路径,组合对象为PathComposite
。
先定义Component
类,即组合中所有对象的基类,定义了组合中对象和组合对象的共有操作。拥有一个操作方法 operation()
,可以在具体的Path
或Composite
中被实现。
再编写Path
(叶节点),即表示组合中的叶子节点对象,它没有子节点。继承自Component
类,实现了operation()
方法,表示基本的操作。
然后编写PathComposite
(复合对象)即表示组合中的复合对象,可以包含其他Path
或Composite
对象。继承自Component
类,包含了真正实现管理子组件的方法,如add(), remove(), getChild()
等,同时也实现了operation()
方法以处理组合对象的操作。
(2) 时序图
图59 组合模式时序图
由上图59可得:
组合模式时序图说明:
客户端先创建具体的Composite
组合对象,继承自Component
类。
再创建叶子对象Path
,继承自Component
类。
接着调用Composite
的join
方法将叶子对象Path
进行拼接得到可访问的路由对象router
。
再逐步将拼接好的路由对象router返回给客户端Client
进行调用和其他操作operation()
。
主程序的流程
图60 组合模式主程序流程图
由上图60可得:程序一开始,客户端先创建路由组合对象
Composite
,接着创建叶子对象Path
,然后调用组合对象Composite
的join
方法将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.basePath
和relativePath
作为参数传递给它,并返回其计算结果。
joinPaths
的代码如下:
图65 组合模式的joinPaths方法代码
代码位置:routergroup.go的128-137行
joinPaths()
是一个函数,接受两个字符串类型的参数absolutePath
和relativePath
,分别表示绝对路径和相对路径。如果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
类型表示单个的路由对象,包含了请求路径Path
、HTTP
方法Method和处理函数Handler。角色: 在组合模式中,
Route
充当叶子对象角色。职责:
表示组合对象的最小单位,不能包含其他对象。包含具体的路由信息和对应的处理逻辑。
AddRoute
方法向路由组中添加子路由
图115 AddRoute代码
AddRoute
是一个方法,它绑定到RouteGroup
结构体上。参数包括path(路由路径)
、method(HTTP 方法)
和handler(处理函数)
。在AddRoute
方法内部,首先创建了一个Route
对象route
。route
是指向Route
结构体的指针。Path
被赋值为传入的 path 参数,表示路由的路径。Method
被赋值为传入的 method 参数,表示路由的 HTTP 方法。Handler
被赋值为传入的 handler 参数,即处理该路由的函数。将路由route添加到 Routes 切片rg.Routes。
主程序 (main 函数):
图116 main方法代码
定义: main 函数作为程序的入口点。
角色: 在组合模式中不直接对应特定角色,但是通过创建和管理
RouteGroup
和Route
对象来实现组合模式中的结构。职责:
创建顶级的
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 方法的注册,例如
PUT
、DELETE
等。遍历当前
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解决方案