基于开闭原则优化数据库查询语句拼接方法

背景

在开发实践中,曾有同事在实现新功能时,因直接修改一段数据库查询条件拼接方法的代码逻辑,导致生产环境出现故障。

具体来看,该方法通过在函数内部直接编写条件判断语句实现查询拼接,尽管从面向对象设计的开闭原则(OCP)出发,理想的代码应满足 “对修改封闭、对扩展开放”—— 即允许通过扩展而非修改原有逻辑来应对变化,但这一规范属于非强制性设计原则,在实际开发中难以确保所有成员始终严格遵守,从而导致新增或调整查询条件时,开发人员更倾向于直接修改原函数,而非通过扩展方式实现,最终埋下代码变更的风险隐患。

func (m ActivityModel) buildQuery(ctx context.Context, db *gorm.DB, filter *ActivityListFilter) {if filter.Name != "" {db = db.Where("name like ?", "%"+filter.Name+"%")}if filter.StartAt > 0 {db = db.Where("start_at <= ?", filter.StartAt)}if filter.EndAt > 0 {db = db.Where("end_at >= ?", filter.EndAt)}if filter.Description != "" {db = db.Where("description like ?", "%"+filter.Description+"%")}if filter.CreatedBy != "" {db = db.Where("created_by like ?", "%"+filter.CreatedBy+"%")}db = db.Where("is_del = ?", filter.IsDel)if filter.CreatedAt > 0 {db = db.Where("create_time > ?", filter.CreatedAt)}if filter.UpdatedAt > 0 {db = db.Where("update_time > ?", filter.UpdatedAt)}if filter.DeletedAt > 0 {db = db.Where("delete_time > ?", filter.DeletedAt)}
}

上述代码中,每个查询条件的添加或修改都需直接操作 buildQuery 函数,违背了开闭原则。为降低维护风险并提升代码扩展性,可通过设计模式将查询条件的逻辑解耦,实现 “对扩展开放,对修改封闭” 的目标。

方案一:使用策略模式

策略模式可以将每个查询条件封装成独立的策略,这样在需要添加新的查询条件时,只需新增一个策略类,而无需修改原有的代码。

package mainimport ("context""github.com/jinzhu/gorm"
)// ActivityModel 定义活动模型
type ActivityModel struct{}// ActivityListFilter 定义过滤条件
type ActivityListFilter struct {Name        stringStartAt     int64EndAt       int64Description stringCreatedBy   stringIsDel       boolCreatedAt   int64UpdatedAt   int64DeletedAt   int64
}// QueryStrategy 定义查询策略接口
type QueryStrategy interface {Apply(db *gorm.DB, filter *ActivityListFilter) *gorm.DB
}// NameQueryStrategy 实现名称查询策略
type NameQueryStrategy struct{}func (n NameQueryStrategy) Apply(db *gorm.DB, filter *ActivityListFilter) *gorm.DB {if filter.Name != "" {return db.Where("name like ?", "%"+filter.Name+"%")}return db
}// StartAtQueryStrategy 实现开始时间查询策略
type StartAtQueryStrategy struct{}func (s StartAtQueryStrategy) Apply(db *gorm.DB, filter *ActivityListFilter) *gorm.DB {if filter.StartAt > 0 {return db.Where("start_at >= ?", filter.StartAt)}return db
}// 可以继续为其他条件实现类似的策略// buildQuery 使用策略模式构建查询
func (m ActivityModel) buildQuery(ctx context.Context, db *gorm.DB, filter *ActivityListFilter) *gorm.DB {strategies := []QueryStrategy{NameQueryStrategy{},StartAtQueryStrategy{},// 添加其他策略}for _, strategy := range strategies {db = strategy.Apply(db, filter)}return db.Where("is_del = ?", filter.IsDel)
}

在这个方案中,每个查询条件都被封装成一个独立的策略,buildQuery 函数通过遍历策略列表来应用这些策略。当需要添加新的查询条件时,只需实现一个新的策略类并将其添加到策略列表中。

优势

  • 解耦条件逻辑:每个策略类单一职责,聚焦特定条件处理,降低代码耦合度。
  • 无缝扩展:新增查询条件时,只需实现 QueryStrategy 接口并添加到策略列表,无需修改核心逻辑。
  • 便于测试:可独立单元测试每个策略,提升测试覆盖率和维护效率。

方案二:使用函数切片

你可以将每个查询条件封装成一个函数,并将这些函数存储在一个切片中。这样,在需要添加新的查询条件时,只需添加一个新的函数到切片中。

package mainimport ("context""github.com/jinzhu/gorm"
)// ActivityModel 定义活动模型
type ActivityModel struct{}// ActivityListFilter 定义过滤条件
type ActivityListFilter struct {Name        stringStartAt     int64EndAt       int64Description stringCreatedBy   stringIsDel       boolCreatedAt   int64UpdatedAt   int64DeletedAt   int64
}// QueryFunc 定义查询函数类型
type QueryFunc func(db *gorm.DB, filter *ActivityListFilter) *gorm.DB// buildQuery 使用函数切片构建查询
func (m ActivityModel) buildQuery(ctx context.Context, db *gorm.DB, filter *ActivityListFilter) *gorm.DB {queryFuncs := []QueryFunc{func(db *gorm.DB, filter *ActivityListFilter) *gorm.DB {if filter.Name != "" {return db.Where("name like ?", "%"+filter.Name+"%")}return db},func(db *gorm.DB, filter *ActivityListFilter) *gorm.DB {if filter.StartAt > 0 {return db.Where("start_at >= ?", filter.StartAt)}return db},// 添加其他查询函数}for _, queryFunc := range queryFuncs {db = queryFunc(db, filter)}return db.Where("is_del = ?", filter.IsDel)
}

在这个方案中,每个查询条件都被封装成一个匿名函数,并存储在 queryFuncs 切片中。buildQuery 函数通过遍历这个切片来应用这些查询函数。当需要添加新的查询条件时,只需添加一个新的匿名函数到切片中。

优势

  • 轻量简洁:无需定义额外接口或类,直接通过匿名函数实现条件封装,适合简单场景。
  • 灵活组合:可动态增删条件函数,支持运行时根据业务需求调整查询逻辑。
  • 代码隔离:每个条件逻辑在独立函数内实现,修改单个条件不会影响其他逻辑。

总结

这两种方案都遵循了开闭原则,使得代码在添加新的查询条件时更加灵活,同时减少了修改现有代码的风险。

  • 策略模式适合条件逻辑复杂、需要多维度扩展或复用的场景,通过接口化设计提升代码规范性。
  • 函数切片则以更轻量的方式实现条件解耦,适合快速开发或条件相对固定的场景。

无论选择哪种方案,核心目标都是将查询条件的 “修改” 操作转化为 “扩展” 操作 —— 新增条件时无需触碰原有逻辑,从架构层面降低人为失误导致的风险。

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

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

相关文章

QT开发工具对比:Qt Creator、Qt Designer、Qt Design Studio

前端开发工具—Qt Designer Qt Designer是Qt框架的一部分&#xff0c;是一个图形用户界面设计工具。它允许开发者通过可视化方式设计和布局GUI组件&#xff0c;而无需手动编写UI代码。设计完成后&#xff0c;Qt Designer生成UI文件&#xff08;通常以.ui为扩展名&#xff09;&…

0基础 | STM32 | TB6612电机驱动使用

TB6612介绍及使用 单片机通过驱动板连接至电机 原因&#xff1a;单品机I/O口输出电流I小 驱动板&#xff1a;从外部引入高电压&#xff0c;控制电机驱动 电源部分 VM&#xff1a;电机驱动电源输入&#xff0c;输入电压范围建议为3.7&#xff5e;12V GND&#xff1a;逻辑电…

【操作系统】死锁

1. 定义 死锁是指两个或多个进程&#xff08;或线程&#xff09;在执行过程中&#xff0c;因争夺资源而造成的一种僵局&#xff0c;每个进程都无限期地等待其他进程释放它们所持有的资源。在这种情况下&#xff0c;没有任何进程能够继续执行&#xff0c;除非有外部干预。 2. …

C++入门☞关于类的一些特殊知识点

涉及的关于类中的默认成员函数的知识点可以看我的这篇博客哦~ C入门必须知道的知识☞类的默认成员函数&#xff0c;一文讲透运用 目录 初始化列表 类型转换 static成员 友元 内部类 匿名对象 对象拷贝时的一些编译器的优化 初始化列表 我们知道类中的构造函数的任务是完…

只用Prettier进行格式化项目

1.下载Prettier插件&#xff0c;禁用ESlint 2.在项目根目录新建.prettierrc文件 {"singleQuote": true,"jsxSingleQuote": true,"printWidth": 100,"trailingComma": "none","tabWidth": 2,"semi": f…

XXL-TOOL v1.4.0 发布 | Java工具类库

Release Notes 1、【新增】JsonRpc模块&#xff1a;一个轻量级、跨语言远程过程调用实现&#xff0c;基于json、http实现&#xff08;从XXL-JOB底层通讯组件提炼抽象&#xff09;。2、【新增】Concurrent模块&#xff1a;一系列并发编程工具&#xff0c;具备良好的线程安全、高…

基于LVGL的登录界面设计

目录 一、演示 二、前言 三、部件知识 3.1 图片按钮部件 3.1.1 图片按钮部件的组成 3.1.2 图片的来源 3.1.3 添加/清除的状态 3.1.4 图片按钮部件 API 函数 3.2 键盘部件(lv_keyboard) 3.2.1 键盘部件的组成 3.2.2 键盘部件的相关知识 3.2.2.1 键盘部件模式 3.…

S3 跨账户复制:增强云中的灾难恢复计划

您准备好提升您的云和 DevOps 技能了吗&#xff1f; &#x1f425;《云原生devops》专门为您打造&#xff0c;我们精心打造的 30 篇文章库&#xff0c;这些文章涵盖了 Azure、AWS 和 DevOps 方法论的众多重要主题。无论您是希望精进专业知识的资深专业人士&#xff0c;还是渴望…

线程与进程深度解析:从fork行为到生产者-消费者模型

线程与进程深度解析&#xff1a;从fork行为到生产者-消费者模型 一、多线程环境下的fork行为与线程安全 1. 多线程程序中fork的特殊性 核心问题&#xff1a;fork后子进程的线程模型 当多线程程序中的某个线程调用fork时&#xff1a; 子进程仅包含调用fork的线程&#xff1…

Circular Plot系列(五): circle plot展示单细胞互作

这是我们circle系列的最后一节&#xff0c;我想常见的弦图是绕不开的&#xff0c;所以最后从前面介绍的circle plot思路&#xff0c;做一遍弦图。其实前面的内容如果消化了&#xff0c;plot互作弦图也就不成什么问题了。 效果如下&#xff1a; #cellchat提取互作结果&#xff…

(11)Vue-Router路由的详细使用

本系列教程目录&#xff1a;Vue3Element Plus全套学习笔记-目录大纲 文章目录 第2章 路由 Vue-Router2.1 Vue路由快速入门2.1.1 创建项目2.1.2 路由运行流程 2.2 传递参数-useRoute2.2.1 路径参数-params1&#xff09;普通传参2&#xff09;传递多个参数3&#xff09;对象方式传…

react + antd 实现后台管理系统

文章目录 完整路由搭建Layout 和 Aside组件引入 AntdAside组件实现 项目效果图 项目完整代码地址 https://gitee.com/lyh1999/react-back-management 项目完整代码地址 react依赖安装 最好采用yarn 安装 react-router 安装依赖 配置路由 history模式 / // src/router/…

基于AWS Marketplace的快速解决方案:从选型到部署实战

1. 引言&#xff1a;为什么选择AWS Marketplace&#xff1f; 在数字化转型的背景下&#xff0c;企业需要快速获取成熟的软件工具和服务以降低开发成本。AWS Marketplace 作为亚马逊云科技的官方应用商店&#xff0c;提供超过万款预配置的第三方和AWS原生解决方案&#xff0c;涵…

2021年第十二届蓝桥杯省赛B组C++题解

2021年第十二届蓝桥杯省赛B组C题解 关键词&#xff1a;蓝桥杯、省赛、题解、C、算法 一、个人见解 第十二届蓝桥杯省赛B组共有10道题目&#xff0c;包含5道填空题&#xff08;T1-T5&#xff09;和5道编程题&#xff08;T6-T10&#xff09;&#xff0c;总分150分。比赛时长4小…

日语学习-日语知识点小记-进阶-JLPT-N1阶段(1):语法单词

日语学习-日语知识点小记-进阶-JLPT-N1阶段&#xff08;1&#xff09;&#xff1a;语法单词 1、前言&#xff08;1&#xff09;情况说明&#xff08;2&#xff09;工程师的信仰&#xff08;3&#xff09;高级语法N1语法和难点一、N1语法学习内容&#xff08;高级语法&#xff…

Python|Pyppeteer实现自动登录小红书(32)

前言 本文是该专栏的第32篇,结合优质项目案例持续分享Pyppeteer的干货知识,记得关注。 本文中,笔者以小红书为例,基于Pyppeteer实现自动登录“小红书”。 需要注意的是,对Pyppeteer不太熟悉的同学,可往前翻阅本专栏前面介绍的Pyppeteer知识点,本专栏将带你了解并熟练使…

【翻译、转载】【转载】LLM 的函数调用与 MCP

来源&#xff1a; https://www.dailydoseofds.com/p/function-calling-mcp-for-llms/ 【代码以图像显示的是原文内容&#xff0c;以代码形式显示的是大模型给出的参考】 LLM 的函数调用与 MCP 在 MCP 变得像现在这样主流&#xff08;或流行&#xff09;之前&#xff0c;大多…

【QT】QT中http协议和json数据的解析-http获取天气预报

QT中http协议和json数据的解析 1.http协议的原理2.QT中http协议的通信流程2.1 方法步骤 3.使用http协议&#xff08;通过http下载图片和获取天气预报信息&#xff09;3.1 http下载网络上的图片(下载小文件)3.1.1 示例代码3.1.2 现象 3.2 获取网络上天气预报3.2.1 免费的天气预报…

hot100:链表倒数k个节点- 力扣(LeetCode)

题目&#xff1a; 实现一种算法&#xff0c;找出单向链表中倒数第 k 个节点。返回该链表中倒数第k个节点。 示例一&#xff1a; 输入&#xff1a;{1,2,3,4,5},2 返回值&#xff1a;{4,5} 说明&#xff1a;返回倒数第2个节点4&#xff0c;系统会打印后面所有的节点来比较。 …

Spring AI 实战:第十一章、Spring AI Agent之知行合一

引言:智能体的知行辩证法 “知为行之始,行为知之成”,王阳明的哲学智慧在AI时代焕发光彩。智能体(LLM Agent)的进化之路,正是"认知-决策-执行"这一闭环的完美诠释: 知明理:融合大语言模型的推理能力与知识图谱的结构化认知行致用:基于ReAct模式的动态工具调…