作为一名开发者,在日常工作中,我们最常面对的可能不是复杂的算法,而是复杂多变的业务逻辑。
是不是经常遇到这种场景:产品经理跑过来说,“我们要接一个新的支付渠道”,或者“针对 VIP 用户加一个新的折扣逻辑”。你打开代码一看,好家伙,原来的代码里已经嵌套了 5 层 if-else,或者是长达几百行的 switch-case。
这时候你的内心是崩溃的:“这代码谁敢动?动一行炸一片啊!”
别慌,今天咱们就来聊聊如何在 Go 语言中优雅地解决这个问题。
我们将通过 表驱动法(Table-Driven) 和 策略模式(Strategy Pattern) ,带你逃离 if-else 的地狱。
那个被 if-else 支配的恐惧
为了方便理解,我们假设一个最常见的场景:订单支付。
我们需要支持支付宝(Alipay)、微信支付(WeChatPay)和银联支付(UnionPay)。
初学者的直觉代码通常是长这样的:
package main
import "fmt"
func Pay(payType string, amount float64) {
if payType == "alipay" {
fmt.Printf("正在使用支付宝支付:%.2f 元\n", amount)
// 这里可能还有几十行支付宝特有的逻辑...
} else if payType == "wechat" {
fmt.Printf("正在使用微信支付:%.2f 元\n", amount)
// 这里可能还有几十行微信特有的逻辑...
} else if payType == "union" {
fmt.Printf("正在使用银联支付:%.2f 元\n", amount)
// 银联的复杂逻辑...
} else {
fmt.Println("未知的支付方式")
}
}
func main() {
Pay("alipay", 100.00)
}这种写法的痛点非常明显:
- 违反开闭原则: 每次新增一种支付方式,你都必须修改
Pay函数的源码。 - 可读性差: 当逻辑变多,
Pay函数会膨胀成几百甚至上千行,不仅难以阅读,更难以进行单元测试。 - 复杂度高: 层层嵌套的判断逻辑,容易让人头晕眼花。
那么,我们如何去优化这样的代码呢?
🚀 进阶第一招:表驱动法(Function Map)
在 Go 语言中,函数是一等公民。这意味着我们可以把函数当作变量存起来。
表驱动法 的核心思想是:把逻辑查找的过程,从 if-else 的线性扫描,变成 Map 的 Key-Value 查找。
我们可以定义一个 Map,Key 是支付类型,Value 是具体的处理函数。
代码示例
package main
import "fmt"
// 定义一个函数类型,统一支付逻辑的签名
type PayHandler func(amount float64)
// 1. 具体的业务逻辑拆分
func payWithAlipay(amount float64) {
fmt.Printf("【支付宝】到账:%.2f 元\n", amount)
}
func payWithWeChat(amount float64) {
fmt.Printf("【微信支付】到账:%.2f 元\n", amount)
}
func payWithUnion(amount float64) {
fmt.Printf("【银联支付】到账:%.2f 元\n", amount)
}
// 2. 初始化分发路由表 (Table)
var payHandlers = map[string]PayHandler{
"alipay": payWithAlipay,
"wechat": payWithWeChat,
"union": payWithUnion,
}
// 3. 统一入口
func Pay(payType string, amount float64) {
// 直接通过 map 查找对应的函数
handler, ok := payHandlers[payType]
if !ok {
fmt.Println("错误:不支持的支付方式")
return
}
// 执行函数
handler(amount)
}
func main() {
Pay("wechat", 88.88)
}这样做的好处:
- O(1) 的查找效率: 无论有多少种支付方式,查找时间都是恒定的。
- 逻辑分离: 每个支付逻辑都在独立的函数里,互不干扰。
- 代码整洁:
Pay主函数非常干净,不再是一坨翔😂
进阶第二招:策略模式(Strategy Pattern)
表驱动法虽然好用,但它通常适用于逻辑相对简单的场景。如果每个支付渠道不仅需要支付,还需要退款、查询、对账等一系列操作,光靠一个函数就不够用了。
这时候,我们需要更强大的武器——策略模式。
在 Go 语言中,接口(Interface) 就是实现策略模式的最佳工具。
1. 定义策略接口
首先,我们要定义一个“支付策略”的标准样子。
// PaymentStrategy 定义了所有支付方式必须实现的方法
type PaymentStrategy interface {
Pay(ctx string, amount float64) error // 支付
Refund(orderID string) error // 退款
}2. 实现具体的策略
接着,我们让每种支付方式都去实现这个接口。
// AlipayStrategy 支付宝策略实现
type AlipayStrategy struct {
// 这里可以包含支付宝特有的配置,比如 AppID, PrivateKey
AppID string
}
func (a *AlipayStrategy) Pay(ctx string, amount float64) error {
fmt.Printf("正在调用支付宝接口 (AppID: %s),金额:%.2f\n", a.AppID, amount)
return nil
}
func (a *AlipayStrategy) Refund(orderID string) error {
fmt.Printf("支付宝退款成功,订单号:%s\n", orderID)
return nil
}
// WeChatStrategy 微信策略实现
type WeChatStrategy struct {}
func (w *WeChatStrategy) Pay(ctx string, amount float64) error {
fmt.Println("正在调用微信支付接口,统一下单...")
return nil
}
func (w *WeChatStrategy) Refund(orderID string) error {
fmt.Println("微信退款申请已提交")
return nil
}3. 上下文管理(Context)与工厂
我们需要一个“管理者”来决定到底使用哪个策略。通常我们可以结合简单工厂模式来使用。
// PaymentContext 支付上下文
type PaymentContext struct {
strategy PaymentStrategy
}
// NewPaymentContext 工厂方法:根据类型创建对应的策略
func NewPaymentContext(payType string) (*PaymentContext, error) {
var strategy PaymentStrategy
// 这里还是免不了一次 switch,但仅限于对象创建,业务逻辑已经剥离了
switch payType {
case "alipay":
strategy = &AlipayStrategy{AppID: "20230001"}
case "wechat":
strategy = &WeChatStrategy{}
default:
return nil, fmt.Errorf("未知的支付方式: %s", payType)
}
return &PaymentContext{strategy: strategy}, nil
}
// ExecutePay 执行支付
func (p *PaymentContext) ExecutePay(amount float64) {
// 多态调用:不需要关心具体是哪个实现
p.strategy.Pay("context_id", amount)
}4. 最终调用
看看现在的调用方式就优雅得多了:
func main() {
// 业务方只需要传入类型
ctx, err := NewPaymentContext("alipay")
if err != nil {
panic(err)
}
// 执行逻辑
ctx.ExecutePay(100.00)
}策略模式的威力:
- 彻底解耦: 具体的支付逻辑(AlipayStrategy)和调用逻辑(PaymentContext)完全分开。
- 易于扩展: 想加一个“银联支付”?只需要新建一个 struct 实现接口,然后在工厂里加一行代码即可,完全不影响现有的支付宝和微信逻辑。
- 易于测试: 你可以轻松写一个
MockStrategy来模拟支付成功或失败,方便做单元测试。
📝 总结
咱们来回顾一下:
- 如果你的分支逻辑非常简单(比如根据状态码返回错误信息),if-else 或 switch 其实完全够用,不要过度设计。
- 如果你的分支逻辑是同质化的函数调用,推荐使用 Map 表驱动法,代码量少且查找快。
- 如果你的分支代表了一整套复杂的行为族(包含多个方法、状态),或者需要支持未来的灵活扩展,策略模式 + 接口 是你的不二之选。
写代码就像搭积木,好的结构能让你搭得更高更稳,而烂的代码只会让你在修 Bug 的路上越走越远。
在实际项目中,你有哪些小技巧呢?一起聊一聊~

