基于开闭原则优化数据库查询语句拼接方法
背景
在开发实践中,曾有同事在实现新功能时,因直接修改一段数据库查询条件拼接方法的代码逻辑,导致生产环境出现故障。
具体来看,该方法通过在函数内部直接编写条件判断语句实现查询拼接,尽管从面向对象设计的开闭原则(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
函数通过遍历这个切片来应用这些查询函数。当需要添加新的查询条件时,只需添加一个新的匿名函数到切片中。
优势:
- 轻量简洁:无需定义额外接口或类,直接通过匿名函数实现条件封装,适合简单场景。
- 灵活组合:可动态增删条件函数,支持运行时根据业务需求调整查询逻辑。
- 代码隔离:每个条件逻辑在独立函数内实现,修改单个条件不会影响其他逻辑。
总结
这两种方案都遵循了开闭原则,使得代码在添加新的查询条件时更加灵活,同时减少了修改现有代码的风险。
- 策略模式适合条件逻辑复杂、需要多维度扩展或复用的场景,通过接口化设计提升代码规范性。
- 函数切片则以更轻量的方式实现条件解耦,适合快速开发或条件相对固定的场景。
无论选择哪种方案,核心目标都是将查询条件的 “修改” 操作转化为 “扩展” 操作 —— 新增条件时无需触碰原有逻辑,从架构层面降低人为失误导致的风险。