【Go-策略模式】告别if/else hell,拥抱 Go 语言策略模式

发布于:2025-07-03 ⋅ 阅读:(30) ⋅ 点赞:(0)
引言:为什么你的代码像一棵巨大的圣诞树?

想象一下,你正在为你的电商平台开发一个订单价格计算模块。最初,需求很简单:商品原价就是最终价格。但很快,业务部门提出了新的需求:

  • 新用户享受 9 折优惠。
  • VIP 用户享受 8 折优惠。
  • 大促活动期间,全场 7 折。

你很自然地写出了这样的代码:

func CalculatePrice(userType string, isPromotion bool, price float64) float64 {
    if userType == "NewUser" {
        return price * 0.9
    } else if userType == "VIP" {
        return price * 0.8
    } else if isPromotion {
        return price * 0.7
    }
    return price // 默认无折扣
}

这段代码看起来没问题,但如果你再想想未来可能的需求:会员日 6 折、积分抵扣、优惠券叠加……你的 if/else 链会像一棵不断长大的圣诞树,挂满了各种复杂的逻辑分支。每一次新需求的到来,你都得修改这个函数,代码变得越来越臃肿、难以阅读和维护。

这就是所谓的 if/else hell。它违反了软件设计中的重要原则——开闭原则(Open-Closed Principle)对扩展开放,对修改封闭。我们如何才能优雅地解决这个问题呢?答案就是——策略模式


什么是策略模式?

策略模式是一种行为型设计模式,它定义了一系列算法(或者说策略),将每一个算法都封装起来,并使它们可以互相替换。

简单来说,它的核心思想是:将算法的实现与使用算法的客户端代码分离开来。客户端不关心具体的算法是如何实现的,它只知道通过一个统一的接口来调用它。这样,你就可以在运行时根据需要动态地选择和切换不同的算法。

想象一下你的手机支付:你可以选择微信支付、支付宝支付或银行卡支付。每种支付方式都是一种策略,它们都实现了“支付”这个功能,但具体流程不同。你作为用户,只需要选择其中一个,点击支付按钮,而不需要关心每种支付方式背后的具体代码逻辑。


策略模式的三个核心组件

策略模式由三个关键角色组成:

  1. 策略接口(Strategy):定义了所有具体策略必须遵循的统一接口。在 Go 语言中,这通常是一个 interface
  2. 具体策略(Concrete Strategy):实现了策略接口的类。每一种具体策略都封装了一种独立的算法或行为。
  3. 上下文(Context):持有对策略接口的引用,并负责调用具体策略来执行任务。它充当了客户端和具体策略之间的桥梁。

Go 语言实现:重构你的订单计算器

现在,让我们使用策略模式来重构上面的订单价格计算代码。

步骤1:定义策略接口

首先,我们定义一个名为 PricingStrategy 的接口,它有一个 Calculate 方法,用来计算最终价格。

// 1. 定义策略接口(Strategy)
// PricingStrategy 定义了所有折扣策略必须实现的接口
type PricingStrategy interface {
    Calculate(originalPrice float64) float64
}
步骤2:创建具体策略

接下来,为每种折扣类型创建独立的结构体,并让它们实现 PricingStrategy 接口。

// 2. 创建具体策略(Concrete Strategy)
// NewUserDiscount 实现了新用户折扣策略
type NewUserDiscount struct{}

func (s *NewUserDiscount) Calculate(originalPrice float64) float64 {
    // 关键点:每种策略只负责自己的计算逻辑
    return originalPrice * 0.9
}

// VIPDiscount 实现了 VIP 用户折扣策略
type VIPDiscount struct{}

func (s *VIPDiscount) Calculate(originalPrice float64) float64 {
    return originalPrice * 0.8
}

// PromotionDiscount 实现了大促折扣策略
type PromotionDiscount struct{}

func (s *PromotionDiscount) Calculate(originalPrice float64) float64 {
    return originalPrice * 0.7
}
步骤3:创建上下文

最后,我们创建一个 PricingContext 结构体,它负责管理和执行策略。

// 3. 创建上下文(Context)
// PricingContext 持有并执行策略
type PricingContext struct {
    strategy PricingStrategy
}

// SetStrategy 动态设置当前使用的策略
func (c *PricingContext) SetStrategy(strategy PricingStrategy) {
    c.strategy = strategy
}

// GetFinalPrice 调用当前策略来计算最终价格
func (c *PricingContext) GetFinalPrice(originalPrice float64) float64 {
    if c.strategy == nil {
        return originalPrice // 如果没有设置策略,返回原价
    }
    // 关键点:上下文不关心是哪种策略,只调用接口方法
    return c.strategy.Calculate(originalPrice)
}

代码使用

现在,我们的代码变得非常灵活和清晰。你可以像搭乐高积木一样,根据需要选择和组合不同的策略。

package main

import "fmt"

func main() {
    // 假设商品原价为 100
    originalPrice := 100.0

    // 实例化上下文
    context := &PricingContext{}

    // 场景一:新用户下单,应用新用户折扣策略
    fmt.Println("--- 场景一:新用户下单 ---")
    context.SetStrategy(&NewUserDiscount{}) // 切换策略
    finalPrice1 := context.GetFinalPrice(originalPrice)
    fmt.Printf("原价: %.2f, 最终价格: %.2f\n", originalPrice, finalPrice1)
    // 输出:原价: 100.00, 最终价格: 90.00

    // 场景二:VIP 用户下单,切换到 VIP 策略
    fmt.Println("\n--- 场景二:VIP 用户下单 ---")
    context.SetStrategy(&VIPDiscount{}) // 轻松切换策略
    finalPrice2 := context.GetFinalPrice(originalPrice)
    fmt.Printf("原价: %.2f, 最终价格: %.2f\n", originalPrice, finalPrice2)
    // 输出:原价: 100.00, 最终价格: 80.00
    
    // 场景三:大促期间,VIP 用户下单,但平台规定大促折扣优先
    fmt.Println("\n--- 场景三:大促期间下单 ---")
    context.SetStrategy(&PromotionDiscount{}) // 再次切换策略
    finalPrice3 := context.GetFinalPrice(originalPrice)
    fmt.Printf("原价: %.2f, 最终价格: %.2f\n", originalPrice, finalPrice3)
    // 输出:原价: 100.00, 最终价格: 70.00

    // 场景四:如果未来新增了“会员日 6 折”策略,你只需要新增一个 struct,无需修改任何旧代码!
    // type MemberDayDiscount struct{}...
}

在这里插入图片描述


优缺点分析

优点 缺点
符合开闭原则:新增策略时,无需修改已有代码。 增加了类的数量:每种策略都需要一个独立的结构体,可能导致类文件增多。
易于扩展:添加新算法只需创建新类型并实现接口。 客户端需要了解所有策略:调用方(如 main 函数)需要知道所有可用的具体策略类。
代码解耦:算法的实现与使用完全分离,职责单一。 如果策略非常简单,过度设计可能带来不必要的复杂性。
运行时切换:可以根据不同条件动态选择和切换算法。

总结

策略模式是一种优雅而强大的设计模式,它帮助我们摆脱了复杂的 if/else 嵌套,使得代码结构更清晰、更易于扩展和维护。在 Go 语言中,利用接口的特性可以轻松实现这一模式。

下次当你遇到需要根据不同条件执行不同行为的场景时,不妨停下来思考一下:这里是不是可以用策略模式来重构呢?用接口和具体的实现来替代一长串的 if/else 分支,让你的代码更具弹性,为未来的扩展做好准备。


网站公告

今日签到

点亮在社区的每一天
去签到