【Gin】智慧架构的巧妙砌筑:Gin框架中控制反转与依赖注入模式的精华解析与应用实战(下)

【Gin】智慧架构的巧妙砌筑:Gin框架中控制反转与依赖注入模式的精华解析与应用实战(下)

大家好 我是寸铁👊
【Gin】智慧架构的巧妙砌筑:Gin框架中控制反转与依赖注入模式的精华解析与应用实战(下)✨
喜欢的小伙伴可以点点关注 💝

在这里插入图片描述


前言

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

在现代软件开发中,控制反转(IoC)和依赖注入(DI)模式作为构建灵活、可扩展系统的重要设计理念,已经成为许多框架和应用中不可或缺的一部分。特别是在Gin框架这样的轻量级、高效的Web框架中,合理运用IoC和DI模式能够显著提升代码的可测试性、可维护性和扩展性,从而帮助开发者构建出更加健壮和高效的应用程序。本文将深入探讨控制反转和依赖注入模式在Gin框架中的精华解析与应用实战,为开发者提供全面的技术指导和实用的应用策略。
控制反转和依赖注入模式通过解耦组件之间的依赖关系,将对象的创建和管理责任交由外部容器来处理,从而有效地降低了系统中各模块之间的耦合度。在Gin框架中,这种模式可以被广泛应用于路由管理、中间件配置以及服务依赖注入等关键领域,使得开发者能够更加灵活地配置和扩展应用的功能和行为。本文旨在通过深入的技术讨论和实例演示,帮助开发者深入理解如何利用IoC和DI模式优化和增强其在Gin框架中的应用程序架构。


关键的类图和时序图

(1) 类图
在Gin框架中,依赖注入具体体现在Gin的引擎对象engineRun方法内部处理业务时,将引擎对象engine注入到Run方法中进行业务的处理。控制反转则体现在处理业务的控制权从Gin的引擎对象enginenet/http库再反转到Gin的引擎对象engine进行真正的业务处理。


依赖注入:
角色说明:

Client:
客户端类,依赖注入的使用者。
持有一个或多个服务对象的引用,通过构造函数注入或者方法注入的方式获取服务实例,并调用服务对象的方法。


Handler:
服务接口,定义了客户端使用的操作接口。


Handler1:
具体的服务实现类,实现了Handler接口的方法。
在这里插入图片描述

图69 依赖注入类图

由上图69可得:先定义客户端类Client,依赖注入的使用者。持有一个或多个服务对象的引用,通过构造函数注入或者方法注入的方式获取服务实例,并调用服务对象的方法。再声明服务接口Handler:定义了客户端使用的操作接口及需要实现的方法Operation()。最后实现多个具体的服务实现类,真正实现了Handler接口的方法Operation()


控制反转:
Client:
客户端类,创建一个处理业务的Gin引擎对象engine


Http:
IoC容器,负责管理和解析依赖对象,用于将依赖注入到engine对象中。


Handler:
服务接口或者抽象类,定义了依赖对象的操作接口。


ConcreteHandler:

真正处理业务对象的类,将控制权从Http中转移到到Gin引擎对象engine。
由Gin引擎对象engine来实现Gin框架内部的真正处理业务的接口Handler来真正处理业务,实现控制权的反转。

在这里插入图片描述

图70 控制反转类图

由上图70可得:先定义Client客户端类,创建一个处理业务的Gin引擎对象engine。再定义Http类:IoC容器,负责管理和解析依赖对象,用于调用http.ListenAndServe(engine)方法将依赖注入到engine对象中。定义Handler服务接口,定义了依赖对象的操作接口,声明了真正处理业务的方法ServeHttp()。最后定义ConcreteHandler类:聚合Engine对象,是真正处理业务对象的类,将控制权从Http中转移到到Gin引擎对象engine。由Gin引擎对象engine来实现Gin框架内部的真正处理业务的接口Handler来真正处理业务,实现控制权的反转。


(2) 时序图
依赖注入:
在这里插入图片描述

图71 依赖注入类图

由上图71可得:Client 在方法中向 Handler接口 发送注入服务对象的请求DependencyHandler 将获取到的依赖对象注入到 ConcreteHandler 中。ConcreteHandler 接收到注入的服务对象后,调用依赖对象的方法执行具体的操作。ConcreteHandler 执行操作后将结果返回给 Handler。最终,结果从 Handler 传递回 Client


控制反转:
在这里插入图片描述

图72 控制反转类图

由上图72可得:客户端Client先创建Engine类对象engine,engine对象拥有一开始的控制权。接着调用http.ListenAndServe(engine)方法将处理业务的控制权转移到Http类,Http类中通过调用engine.ServeHTTP()方法将控制权反转给Engine类对象。


主程序的流程

依赖注入:

由下图73可得:程序开始,Client客户端注入依赖Dependency请求到Handler业务对象,Handler业务对象将依赖转发到真正处理业务的对象ConcreteHandler,接着真正处理业务的对象ConcreteHandler处理业务,真正处理业务的对象ConcreteHandler处理业务完毕后,沿着调用顺序依次返回处理结果给Client客户端,程序结束。

在这里插入图片描述

图73 依赖注入主程序流程图

控制反转:

由下图74可得:程序开始,Client客户端先创建Gin引擎对象engine,engine对象具有处理业务的控制权。如果控制权未反转,则将控制权转移到HTTP对象中。之后反转控制权将控制权反转给engine对象,控制权反转后,engine对象实现Gin框架内部的ServeHTTP()方法来真正处理业务,业务处理完毕后,任务结束。

图74 控制反转主程序流程图

在这里插入图片描述

程序模块之间的调用关系

在这里插入图片描述

图75 依赖注入和控制反转调用剖析图

由上图75可得:
Gin 框架的依赖注入和控制反转,涉及的角色如下:
依赖注入:
engine.Handler() 方法的调用体现了依赖注入的思想。在 http.ListenAndServe(address, engine.Handler()) 中,engine.Handler() 返回了一个实现了 http.Handler 接口的具体对象,这个对象可能是一个路由处理器或中间件链的实例。
Engine 结构体的设计允许将不同的处理逻辑(如路由、中间件等)注入到Run()方法中。这种方式使得 Run() 方法可以处理各种不同的请求处理逻辑,而不需要直接在 Run() 方法内部硬编码这些逻辑。


控制反转:

控制反转的核心思想是将对象的创建和管理权力反转给外部。在这段代码中,http.ListenAndServe(address, engine.Handler()) 中的 http.ListenAndServe 是一个框架外部的方法,它接受一个 http.Handler 对象作为参数。
Run() 方法并不直接控制和管理 http.Server 的创建和启动过程,而是将这一职责委托给了 http.ListenAndServe 函数。框架的用户可以通过传入不同的处理器(即 engine.Handler() 返回的对象)来定制 HTTP 服务器的行为,这样实现了控制反转。
角色说明:
Engine 结构体的方法 Run() 扮演了主动者的角色,负责组织和启动整个框架的运行流程。它通过依赖注入的方式获取所需的处理器,并将控制权转移给外部的 http.ListenAndServe 方法,实现了控制反转。

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

依赖注入:
客户端调用router.Run(":8883")方法启动HTTP服务器,Run方法内部http.ListenAndServe(address, engine.Handler())处理业务对象为router,从外部传入,依赖于外部的engine对象,而非本方法内自己创建的对象。
控制反转:
客户端调用router.Run(":8883")方法启动HTTP服务器,Gin框架启动时,将处理对象的控制权移交给net/http的http.ListenAndServe(address, engine.Handler())处理,net/http的http.ListenAndServe(address, engine.Handler())不对Gin框架的业务进行处理,而是将Gin框架的控制权移交给Gin框架原来的engine对象engine.Handler()进行处理,engine.Handler()实现Gin框架内部自己定义的ServeHttp()方法进行业务处理,实现控制反转。
在上图的基础上,下面对各个模块的代码进行深入剖析:
依赖注入:
客户端调用Run方法,启动HTTP服务器。

整理依赖注入和控制反转模型如下:
在这里插入图片描述

图76 依赖注入和控制反转模型图


在这里插入图片描述

图77客户端调用Run方法


在这里插入图片描述

图78 engine对象的 Run方法
代码位置:gin.go的389行、399行

Run方法中调用http.ListenAndServe(address, engine.Handler())http.ListenAndServe 是 Go 标准库 net/http 提供的一个函数,用于启动一个 HTTP 服务器,监听指定的地址。address 是一个字符串参数,表示服务器监听的地址和端口号,如 ":8080"engine.Handler() 是 Engine 结构体的方法,返回一个实现了 http.Handler 接口的对象。在 Gin 框架中,这个对象通常是处理所有 HTTP 请求的核心组件,包括路由处理器和中间件链。http.ListenAndServe(address, engine.Handler()) 的作用是启动一个 HTTP 服务器,并将请求交给 engine.Handler() 处理,体现了依赖注入的思想。

控制反转:
同样地,客户端调用Run方法,启动HTTP服务器。
在这里插入图片描述

图79 客户端调用Run方法


Gin框架启动时,将处理对象的控制权移交给net/httphttp.ListenAndServe(address, engine.Handler())处理。
在这里插入图片描述

图80 engine对象的 Run方法
代码位置:gin.go的389行、399行

net/http的http.ListenAndServe(address, engine.Handler())不对Gin框架的业务进行处理。


在这里插入图片描述

图81 engine的Handler()方法
代码位置:gin.go的228-235行

228行:这是一个 Engine 结构体的方法定义,返回类型是 http.Handler 接口。这意味着 Handler() 方法返回一个实现了 http.Handler 接口的对象,可以处理 HTTP 请求。

229行:if !engine.UseH2C {engine.UseH2C 是 Engine 结构体中的一个布尔类型的字段或属性。根据其值来决定返回什么样的 http.Handler 对象。return engine如果 engine.UseH2C 的值为 false,则直接返回 engine 自身。在 Gin 框架中,engine 实际上是 Engine 结构体的实例,它本身已经实现了 http.Handler 接口(通常是一个 http.ServeMux,即路由器)。

233行:h2s := &http2.Server{}
如果 engine.UseH2C 的值为 true,则创建一个新的 http2.Server 的实例,并将其赋值给 h2s 变量。http2.Server 是 Go 标准库中用于支持 HTTP/2 协议的服务器端的结构体。

234行:return h2c.NewHandler(engine, h2s)
使用h2c.NewHandler函数创建一个新的处理器。h2c 是一个库,用于将 HTTP/2 请求映射到 HTTP/1 处理程序。该函数接受两个参数:engine:传入当前的 Engine 实例,作为 HTTP/1 处理程序的实现。h2s:传入前面创建的 http2.Server 实例,用于支持 HTTP/2 协议的相关设置和处理。返回的对象也是一个 http.Handler 接口,这样可以保证 Handler() 方法返回的对象在不同的协议版本(HTTP/1 或 HTTP/2)之间可以透明切换,根据配置的 engine.UseH2C 自动选择合适的处理方式。


依赖注入和控制反转案例及调试分析

依赖注入是一种设计模式,它通过外部实体来提供一个类或对象所依赖的对象。简单来说,就是把依赖关系从一个对象转移到另一个对象,从而实现解耦和灵活性。控制反转是一种设计原则,它反转了传统的程序控制流程,将控制权交给了外部系统或容器。即,不再由调用方决定依赖对象的获取方式,而是由外部容器管理和提供依赖。


基于上述描述,编写测试案例如下:
在这里插入图片描述

图121 定义UserService服务接口
UserService 是一个接口,定义了获取用户信息的方法 GetUser。返回客户端输入的用户ID


在这里插入图片描述

图122 声明UserService的一个实现
UserServiceImpl 结构体实现了UserService接口,即它必须实现 GetUser(userID string) string 方法。


在这里插入图片描述

图123 定义GetUser()方法
UserServiceImpl 结构体实现了GetUser(userID string) string方法,这里假设它通过 userID 获取用户信息并返回字符串格式化的结果。


在这里插入图片描述

图124 编写客户端代码

在 main 函数中,首先创建了一个 Gin 实例 router。控制台输出控制权由Gin管理这句话,用于观察控制反转。创建了userService实例,这里是 UserServiceImpl 的一个对象。调用GET方法,打印控制权由net/http管理这句话,用于观察控制反转。使用依赖注入的方式,将 userService 注入到 Gin 的路由处理器中当收到GET /user/:id的请求时,从 URL 中获取 id 参数,并调用 userService.GetUser(userID) 方法来获取用户信息。将用户信息作为字符串响应给客户端。打印控制权由Gin管理,用于观察控制反转。


调试分析:
启动测试案例的服务端,完成对象的依赖注入,并等待客户端请求的发送。运行测试依赖注入与控制反转案例成功!

在这里插入图片描述

图125启动测试案例成功
使用API测试工具APIfox,向服务器监听的端口发送GET请求,并得到服务端发送的响应信息,依赖对象处理业务成功,并将响应信息回写给客户端,调试依赖注入和控制反转案例成功!


在这里插入图片描述

图126 Apifox发起GET请求


依赖注入和控制反转测试结果

使用API测试工具APIfox,向服务器监听的端口发送GET请求,并得到服务端发送的响应信息,依赖对象处理业务成功,并将响应信息回写给客户端,测试依赖注入案例成功!

在这里插入图片描述

图146依赖注入客户端测试结果

接下来观察控制台的输出信息是否和预期的一致,一致则说明实现了控制反转:
先输出:控制权由Gin管理
再输出:控制权由net/http管理
最后又输出:控制权由Gin管理
观察发现,控制台输出顺序与预估的一致:
先输出:控制权由Gin管理
再输出:控制权由net/http管理
最后又输出:控制权由Gin管理
说明实现控制反转成功!

在这里插入图片描述

图147 控制反转测试结果


结语

通过本文的探讨与分析,我们详细探索了控制反转和依赖注入模式在Gin框架中的应用与实践。这些模式不仅能够有效地简化复杂系统的设计与维护,还能够提升系统的灵活性和可扩展性,使得开发者能够更加轻松地应对变化和挑战。在实际项目中,合理运用控制反转和依赖注入模式能够显著提高代码的质量和可读性,为Gin框架应用的长期发展奠定坚实的技术基础。希望本文能够为广大开发者提供有益的参考和实用的指导,帮助他们在实际应用中充分发挥IoC和DI模式的优势,打造出更加强大和灵活的软件系统。


看到这里的小伙伴,恭喜你又掌握了一个技能👊
希望大家能取得胜利,坚持就是胜利💪
我是寸铁!我们下期再见💕


在这里插入图片描述

往期好文💕

保姆级教程

【保姆级教程】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稳定的有序遍历的方式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/876730.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

怀旧必玩!重返童年,扫雷游戏再度登场!

Python提供了一个标准的GUI(图形用户界面)工具包:Tkinter。它可以用来创建各种窗口、按钮、标签、文本框等图形界面组件。 而且Tkinter 是 Python 自带的库,无需额外安装。 Now,让我们一起来回味一下扫雷小游戏吧 扫…

快速搞定分布式RabbitMQ---RabbitMQ进阶与实战

本篇内容是本人精心整理;主要讲述RabbitMQ的核心特性;RabbitMQ的环境搭建与控制台的详解;RabbitMQ的核心API;RabbitMQ的高级特性;RabbitMQ集群的搭建;还会做RabbitMQ和Springboot的整合;内容会比较多&#…

【C++】C++入门知识(上)

好久不见&#xff0c;本篇介绍一些C的基础&#xff0c;没有特别的主题&#xff0c;话不多说&#xff0c;直接开始。 1.C的第一个程序 C中需要把定义文件代码后缀改为 .cpp 我们在 test.cpp 中来看下面程序 #include <stdio.h> int main() {printf("hello world\n…

SQL Server 设置端口号:详细步骤与注意事项

目录 一、了解SQL Server端口号的基础知识 1.1 默认端口号 1.2 静态端口与动态端口 二、使用SQL Server配置管理器设置端口号 2.1 打开SQL Server配置管理器 2.2 定位到SQL Server网络配置 2.3 修改TCP/IP属性 2.4 重启SQL Server服务 三、注意事项 3.1 防火墙设置 3…

Java小抄|Java中的List与Map转换

文章目录 1 List<User> 转Map<User.id,User>2 基础类型的转换&#xff1a;List < Long> 转 Map<Long,Long> 1 List 转Map<User.id,User> Map<Long, User> userMap userList.stream().collect(Collectors.toMap(User::getId, v -> v, …

p28 vs环境-C语言实用调试技巧

int main() { int i0; for(i0;i<100;i) { printf("%d",i); } } 1.Debug 和Release的介绍 Debug通常称为调试版本&#xff0c;它包含调试信息&#xff0c;并且不做任何优化&#xff0c;便于程序员调试程序。 Release称为发布版本&#x…

PTPD 在 QNX 系统上的授时精度验证与误差排查

文章目录 0. 引言1.关键函数实现2. 验证策略与结果3. 授时误差的排查与解决3. 授时误差的排查与解决4. 结论 0. 引言 PTPD是一种时间同步的开源实现&#xff0c;在不同操作系统上的表现可能存在显著差异。 本文通过在QNX系统上运行PTPD&#xff0c;针对其授时精度进行详细验证…

探索算法系列 - 双指针

目录 移动零&#xff08;原题链接&#xff09; 复写零&#xff08;原题链接&#xff09; 快乐数&#xff08;原题链接&#xff09; 盛最多水的容器&#xff08;原题链接&#xff09; 有效三角形的个数&#xff08;原题链接&#xff09; 查找总价格为目标值的两个商品&…

优化算法:2.粒子群算法(PSO)及Python实现

一、定义 粒子群算法&#xff08;Particle Swarm Optimization&#xff0c;PSO&#xff09;是一种模拟鸟群觅食行为的优化算法。想象一群鸟在寻找食物&#xff0c;每只鸟都在尝试找到食物最多的位置。它们通过互相交流信息&#xff0c;逐渐向食物最多的地方聚集。PSO就是基于这…

【python_将一个列表中的几个字典改成二维列表,并删除不需要的列】

def 将一个列表中的几个字典改成二维列表(original_list,headersToRemove_list):# 初始化一个列表用于存储遇到的键&#xff0c;保持顺序ordered_keys []# 遍历data中的每个字典&#xff0c;添加其键到ordered_keys&#xff0c;如果该键还未被添加for d in original_list:for …

P4009 汽车加油行驶问题题解

P4009 汽车加油行驶问题 紫题&#xff0c;但是DFS。 思路 记忆化搜索&#xff0c;分多钟情况去搜索。 注意该题不用标记&#xff0c;有可能会往回走。 有可能这样走。 代码 #include<bits/stdc.h> #include<cstring> #include<queue> #include<set&g…

redis:清除缓存的最简单命令示例

清除redis缓存命令(执行命令列表见截图) 1.打开cmd窗口&#xff0c;并cd进入redis所在目录 2.登录redis redis-cli 3.查询指定队列当前的记录数 llen 队列名称 4.清除指定队列所有记录 ltrim 队列名称 1 0 5.再次查询&#xff0c;确认队列的记录数是否已清除

配置和连接另一台电脑上的 MySQL 数据库

要配置和连接另一台电脑上的 MySQL 数据库&#xff0c;可以按照以下步骤进行设置&#xff1a; 1. 配置 MySQL 服务器 在目标计算机上&#xff08;192.168.10.103&#xff09;进行以下操作&#xff1a; 修改 MySQL 配置文件&#xff1a; 打开 MySQL 配置文件&#xff08;通常位…

【系统架构设计师】十八、信息系统架构设计理论与实践①

目录 一、信息系统架构概述 二、信息系统架构风格与分类 2.1 信息系统架构风格 2.2 信息系统架构分类 三、信息系统架构模型 3.1 单体应用 3.2 客户机/服务器 3.2.1 二层 C/S 3.2.2 三层 C/S 和 B/S 3.2.3 多层 C/S 和 B/S 3.2.4 MVC 3.3 面向服务架构(SOA)模式 …

Activiti 本地画流程 http://localhost:8080/activiti-app/#/

http://localhost:8080/activiti-app/#/ 1、本地安装了Tomcat 2、本地安装了Activiti 3、拷贝Activiti中这两个文件到Tomcat中的webapps目录下 4、启动startu.bat 5、http://localhost:8080/activiti-app/#/ 账号&#xff1a;admin 密码&#xff1a;test

乐鑫 Matter 技术体验日回顾|全面 Matter 解决方案驱动智能家居新未来

日前&#xff0c;乐鑫信息科技 (688018.SH) 在深圳成功举办了 Matter 方案技术体验日活动&#xff0c;吸引了众多照明电工、窗帘电机、智能门锁、温控等智能家居领域的客户与合作伙伴。活动现场&#xff0c;乐鑫产研团队的小伙伴们与来宾围绕 Matter 产品研发、测试认证、生产工…

Python学习笔记46:游戏篇之外星人入侵(七)

前言 到目前为止&#xff0c;我们已经完成了游戏窗口的创建&#xff0c;飞船的加载&#xff0c;飞船的移动&#xff0c;发射子弹等功能。很高兴的说一声&#xff0c;基础的游戏功能已经完成一半了&#xff0c;再过几天我们就可以尝试驾驶 飞船击毁外星人了。当然&#xff0c;计…

解析西门子PLC的String和WString

西门子PLC有两种字符串类型&#xff0c;String与WString String 用于存放英文数字标点符号等ASCII字符&#xff0c;每个字符占用一个字节 WString宽字符串用于存放中文、英文、数字等Unicode字符&#xff0c;每个字符占用两个字节 之前我搞过一篇解析String的 关于使用TCP-…

Vue3 Pinia的创建与使用代替Vuex 全局数据共享 同步异步

介绍 提供跨组件和页面的共享状态能力&#xff0c;作为Vuex的替代品&#xff0c;专为Vue3设计的状态管理库。 Vuex&#xff1a;在Vuex中&#xff0c;更改状态必须通过Mutation或Action完成&#xff0c;手动触发更新。Pinia&#xff1a;Pinia的状态是响应式的&#xff0c;当状…

Linux内核 mmap内存映射的实现原理

在Linux内核以及Linux系统编程的时候&#xff0c;经常会碰到mmap内存映射&#xff0c;mmap函数是实现高性能编程的一个关键点。本文详细介绍一下mmap实现原理。 虚拟地址映射物理地址 虚拟地址映射物理地址采用的是页表机制&#xff0c;64位CPU采用的是4级页表。 64位CPU虚拟…