悠悠楠杉
Golang如何实现策略模式与条件判断结合:策略模式优化示例
go
func search(content string, query string, matchType string) float64 {
if matchType == "title" {
return calculateTitleScore(content, query)
} else if matchType == "keywords" {
return calculateKeywordScore(content, query)
} else if matchType == "description" {
return calculateDescriptionScore(content, query)
} else if matchType == "body" {
return calculateBodyScore(content, query)
}
return 0.0
}
这种写法看似简单,但一旦新增一种匹配类型或修改评分逻辑,就必须修改原有函数,违反了“开闭原则”。更严重的是,当匹配逻辑本身变得复杂(例如引入模糊匹配、词干提取、TF-IDF 算法等),这个函数将迅速膨胀。
策略模式的核心思想
策略模式的核心在于将算法的使用与实现分离。我们定义一个统一的接口来表示评分策略,每个具体策略实现该接口。调用方无需关心具体实现细节,只需传入合适的策略对象即可。
在 Go 中,我们可以这样设计:
go
type ScoringStrategy interface {
Score(content, query string) float64
}
type TitleScorer struct{}
func (t *TitleScorer) Score(content, query string) float64 {
// 标题匹配:完全匹配加分,部分匹配按比例
if strings.Contains(strings.ToLower(content), strings.ToLower(query)) {
if strings.EqualFold(content, query) {
return 1.0
}
return 0.7
}
return 0.0
}
type KeywordScorer struct{}
func (k *KeywordScorer) Score(content, query string) float64 {
// 关键词匹配:逗号分隔,精确匹配计分
keywords := strings.Split(content, ",")
for _, kw := range keywords {
if strings.EqualFold(strings.TrimSpace(kw), query) {
return 0.9
}
}
return 0.0
}
type DescriptionScorer struct{}
func (d *DescriptionScorer) Score(content, query string) float64 {
// 描述匹配:允许一定模糊度,使用子串匹配
return float64(strings.Count(strings.ToLower(content), strings.ToLower(query))) * 0.3
}
type BodyScorer struct{}
func (b *BodyScorer) Score(content, query string) float64 {
// 正文匹配:基于词频,每出现一次加0.1分,上限0.5
count := strings.Count(strings.ToLower(content), strings.ToLower(query))
score := float64(count) * 0.1
if score > 0.5 {
return 0.5
}
return score
}
结合条件判断进行动态选择
策略模式并不排斥条件判断,而是将其从核心逻辑中剥离出来,集中管理。我们可以通过一个工厂函数,根据输入类型返回对应的策略实例:
go
func GetScorer(matchType string) ScoringStrategy {
switch matchType {
case "title":
return &TitleScorer{}
case "keywords":
return &KeywordScorer{}
case "description":
return &DescriptionScorer{}
case "body":
return &BodyScorer{}
default:
return nil
}
}
此时,主搜索逻辑变得极为清晰:
go
func SearchWithStrategy(content, query, matchType string) float64 {
scorer := GetScorer(matchType)
if scorer == nil {
return 0.0
}
return scorer.Score(content, query)
}
所有复杂的判断都被封装在 GetScorer 中,而评分逻辑则分散在各自的策略结构体中。这种结构不仅提升了代码的可读性,也极大增强了可扩展性。
进一步优化:策略注册表
为了彻底消除条件判断带来的耦合,我们可以引入策略注册机制,利用 Go 的 init 函数自动注册策略:
go
var scorers = make(map[string]ScoringStrategy)
func RegisterScorer(name string, scorer ScoringStrategy) {
scorers[name] = scorer
}
func GetScorerFromRegistry(name string) ScoringStrategy {
if scorer, exists := scorers[name]; exists {
return scorer
}
return nil
}
// 各策略包内自动注册
func init() {
RegisterScorer("title", &TitleScorer{})
RegisterScorer("keywords", &KeywordScorer{})
RegisterScorer("description", &DescriptionScorer{})
RegisterScorer("body", &BodyScorer{})
}
这种方式下,新增策略只需实现接口并调用 RegisterScorer,无需改动任何已有代码,真正实现了“对扩展开放,对修改关闭”。
实际应用场景与优势
在真实项目中,这种模式广泛应用于:
- 搜索引擎的多维度打分系统
- 支付渠道的路由选择
- 数据导出格式(CSV、JSON、Excel)的生成
- 用户权限校验的不同策略组合
其优势体现在:
- 高内聚低耦合:每个策略独立封装,互不影响。
- 易于测试:可针对每个策略编写单元测试,无需模拟复杂条件分支。
- 动态切换:运行时可根据配置动态选择策略。
- 便于监控与日志:可在策略外层统一添加耗时统计、日志记录等横切逻辑。
总结
策略模式并非要完全取代条件判断,而是将其从核心业务逻辑中解耦出来,使代码结构更加清晰、灵活。在 Golang 中,借助接口和映射机制,我们能以极简的方式实现这一经典设计模式。当面对多重条件分支时,不妨先问自己:这些分支是否代表不同的“策略”?如果是,那么策略模式很可能就是你重构之路的起点。
