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

news2024/9/23 19:18:17

【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稳定的有序遍历的方式

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

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

相关文章

Typora 【最新1.8.6】版本安装下载教程 (轻量级 Markdown 编辑器),图文步骤详解,免费领取(软件可激活使用)

文章目录 软件介绍软件下载安装步骤激活步骤 软件介绍 Typora 是一款专为 Markdown 爱好者设计的文本编辑器,它结合了简洁的界面设计与强大的 Markdown 渲染能力,为用户提供了一个流畅、高效的写作环境。以下是对 Typora 更详细的介绍: 核心特…

[k8s源码]8.deltaFIFO

deltaFIFO DeltaFIFO: 这是一个特殊类型的队列,它结合了FIFO(先进先出)队列的特性和增量(Delta)处理的能力。DeltaFIFO 中是按顺序存储的,但它们不必严格按照发生的顺序逐个处理。这种设计提供了处理的灵…

iPhone 17系列取消17 Plus版本?新一代苹果手机迎来新变革

随着科技的飞速发展,苹果公司再次准备刷新我们的期待,即将推出的iPhone 17系列携带着一系列令人兴奋的升级。今年,苹果打破了常规,将四款新机型带入市场——iPhone 17、17 Pro、17 Pro Max,以及一款全新的成员&#xf…

QT串口和数据库通信

创建串口 串口连接客户端并向服务器发送消息 client.pro #------------------------------------------------- # # Project created by QtCreator 2024-07-02T14:11:20 # #-------------------------------------------------QT core gui network QT core gui…

Music Tag Editor Pro for Mac:强大的音频标签管理工具

Music Tag Editor Pro for Mac是一款专为Mac系统设计的音频标签管理工具,其简易直观的操作界面和强大的功能深受用户喜爱。 这款软件的核心功能在于它能够批量编辑各类音频文件的标签。无论是修改元数据、重命名文件,还是转换音乐标签的文本编码&#x…

(十三)Spring教程——依赖注入之工厂方法注入

1.工厂方法注入 工厂方法是在应用中被经常使用的设计模式,它也是控制反转和单例设计思想的主要实现方法。由于Spring IoC容器以框架的方式提供工厂方法的功能,并以透明的方式开放给开发者,所以很少需要手工编写基于工厂方法的类。正是因为工厂…

参加可观测性Observability Foundation认证培训,您有哪些收益?

一、可观测性认证培训的内容 作为SRE(站点可靠性工程)课程体系的最新发展,可观测性(Observability)认证课程介绍了一系列结合应用程序生命周期和复杂体系架构中推进可观测性的核心概念和实践。为关注全栈可观测性&…

Hyper-V 安装 CentOS 8.5

前言 Hyper-V安装文档:在 Windows 10 上安装 Hyper-VCentOS 系统下载:CentOS 国内镜像源 8.5.2111作者:易墨发布时间:2023.10.01原文地址:https://www.cnblogs.com/morang/p/devops-hyperv-centos-install.html使用命令安装 以管理员身份运行 PowerShell 命令: Enable-…

【React】全面解析:从基础知识到高级应用,掌握现代Web开发利器

文章目录 一、React 的基础知识1. 什么是 React?2. React 的基本概念3. 基本示例 二、React 的进阶概念1. 状态(State)和属性(Props)2. 生命周期方法(Lifecycle Methods)3. 钩子(Hoo…

pikauchu之Unsafe Fileupload(不安全的文件上传)

Client check&#xff08;客户检查&#xff09; 第一步先新建一个一句话木马 <?php eval($_POST[1]);?> 然后上传文件 有限制&#xff0c;只能上传那几种类型 现在看看源代码 我们将一句话木马文件的后缀改为png 然后用burp抓包&#xff0c;将png改成php 就能上传成功 …

Android中Service学习记录

目录 一 概述二 生命周期2.1 启动服务startService()2.2 绑定服务bindService()2.3 先启动后绑定2.4 先绑定后启动 三 使用3.1 本地服务&#xff08;启动式&#xff09;3.2 可通信的服务&#xff08;绑定式&#xff09;3.3 前台服务3.4 IntentService 总结参考 一 概述 Servic…

无人机之起飞前准备

一、检查无人机状态 1、确保无人机的电池充满电或有足够的电量&#xff1b; 2、检查螺旋桨是否安装牢固&#xff0c;没有损坏&#xff1b; 3、确认无人机的固件是最新版本&#xff0c;以保证拥有最新的功能和修正。 二、选择合适的起飞地点 1、避免在人群密集或有障碍物的…

husky引发git commit报错的解决方案

在git commit的时候&#xff0c;有可能会遇到这样的报错&#xff0c;husky - pre-commit hook exited with code 1 (error) 出现这个问题的原因主要是&#xff0c;假如项目中采用 husky和lint-staged结合进行代码校验&#xff0c;那么&#xff0c;只要项目代码中有不规范的地方…

新手学习python 安装Anaconda

PyCharm选择社区版就够用了&#xff0c;专业版本太多花哨的东西没必要&#xff0c;环境可以用Anaconda&#xff0c;单纯的python环境需要下载很多包 一&#xff1a;安装Anaconda 下载打包坏境 Anaconda 地址 https://www.anaconda.com/&#xff0c;我本地是安装在D:\ProgramD…

CTF ssti零基础(一) 模块注入的payload

这里先推荐一个还不错的工具 git clone https://github.com/vladko312/SSTImap.gitcd SSTImappip install requirements.txt命令还是挺多的 有点像sqlmap但是这个暴力多了不用加那么多的参数 ./sstimap.py -u http://example.com ./sstimap.py -u http://example.com --os-sh…

Word 导入导出

在实际的开发过程中&#xff0c;也会遇到导入导出的功能&#xff0c;今天就简单的做一下总结。 1.需求&#xff1a;将下面word 数据导入到数据库并进行存储 在Controller中 RequestMapping(value "/ImportWord")public RawResponseBodyObject ImportWord(HttpServl…

怎么将jpg图片转换为pdf?将jpg图片转换为pdf的几种方法介绍

怎么将jpg图片转换为pdf&#xff1f;转换jpg图片为PDF是一项常见的任务&#xff0c;无论是个人用户还是商业环境中都经常会遇到这种需求。将多张jpg图像合并成一个单一的PDF文件可以带来诸多便利&#xff0c;例如便于存档、分享和打印。这种转换不仅简化了文件管理流程&#xf…

萝卜快跑:自动驾驶的先锋与挑战

萝卜快跑&#xff1a;自动驾驶的先锋与挑战 近段时间&#xff0c;由萝卜快跑引发的自动驾驶事件如火如荼&#xff0c;成为科技领域的热门话题。萝卜快跑作为自动驾驶领域的重要参与者&#xff0c;其最新事件引发了广泛的关注和讨论。 萝卜快跑是百度推出的自动驾驶出行服务平台…

计算机网络基础:4.HTTP与HTTPS

一、回顾设定 想象你在经营一家繁忙的餐厅&#xff0c;顾客们通过点餐系统&#xff08;网卡&#xff09;下单&#xff0c;订单被前台&#xff08;路由器&#xff09;接收并分发到各个厨房区域&#xff08;网络设备&#xff09;。光猫像是食材供应商&#xff0c;通过高效的物流系…

2、LangChain —— RAG基本架构

文章目录 一、概述二、什么是 RAG三、概念1、Indexing2、Retrieval and generation 四、设置1、下载安装 langchain2、LangSmith 五、一个简单的RAG后端1、Load2、Split3、Embedding and Store4、Retrieval5、Generate 一、概述 LLM 支持的最强大的应用程序之一是 复杂的问答 (…