在开发实践中,曾有同事在实现新功能时,因直接修改一段数据库查询条件拼接方法的代码逻辑,导致生产环境出现故障。
具体来看,该方法通过在函数内部直接编写条件判断语句实现查询拼接,尽管从面向对象设计的开闭原则(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 main
import (
"context"
"github.com/jinzhu/gorm"
)
// ActivityModel 定义活动模型
type ActivityModel struct{}
// ActivityListFilter 定义过滤条件
type ActivityListFilter struct {
Name string
StartAt int64
EndAt int64
Description string
CreatedBy string
IsDel bool
CreatedAt int64
UpdatedAt int64
DeletedAt 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 main
import (
"context"
"github.com/jinzhu/gorm"
)
// ActivityModel 定义活动模型
type ActivityModel struct{}
// ActivityListFilter 定义过滤条件
type ActivityListFilter struct {
Name string
StartAt int64
EndAt int64
Description string
CreatedBy string
IsDel bool
CreatedAt int64
UpdatedAt int64
DeletedAt 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
函数通过遍历这个切片来应用这些查询函数。当需要添加新的查询条件时,只需添加一个新的匿名函数到切片中。
优势:
这两种方案都遵循了开闭原则,使得代码在添加新的查询条件时更加灵活,同时减少了修改现有代码的风险。
无论选择哪种方案,核心目标都是将查询条件的 “修改” 操作转化为 “扩展” 操作 —— 新增条件时无需触碰原有逻辑,从架构层面降低人为失误导致的风险。