背景
在開發實踐中,曾有同事在實現新功能時,因直接修改一段數據庫查詢條件拼接方法的代碼邏輯,導致生產環境出現故障。
具體來看,該方法通過在函數內部直接編寫條件判斷語句實現查詢拼接,盡管從面向對象設計的開閉原則(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
?函數通過遍歷這個切片來應用這些查詢函數。當需要添加新的查詢條件時,只需添加一個新的匿名函數到切片中。
優勢:
- 輕量簡潔:無需定義額外接口或類,直接通過匿名函數實現條件封裝,適合簡單場景。
- 靈活組合:可動態增刪條件函數,支持運行時根據業務需求調整查詢邏輯。
- 代碼隔離:每個條件邏輯在獨立函數內實現,修改單個條件不會影響其他邏輯。
總結
這兩種方案都遵循了開閉原則,使得代碼在添加新的查詢條件時更加靈活,同時減少了修改現有代碼的風險。
- 策略模式適合條件邏輯復雜、需要多維度擴展或復用的場景,通過接口化設計提升代碼規范性。
- 函數切片則以更輕量的方式實現條件解耦,適合快速開發或條件相對固定的場景。
無論選擇哪種方案,核心目標都是將查詢條件的 “修改” 操作轉化為 “擴展” 操作 —— 新增條件時無需觸碰原有邏輯,從架構層面降低人為失誤導致的風險。