Factory 和 Strategy 应在需动态创建算法实例且算法内部依赖可替换行为时组合使用,如支付模块根据 payType 创建不同策略并注入签名器等依赖。
当你的业务逻辑需要根据运行时参数动态创建不同算法实现,且这些算法本身又需要封装可替换的行为时,Factory + Strategy 是最自然的组合。比如支付模块:用户选择微信支付或支付宝,系统要创建对应的支付策略实例,而每个策略内部还可能依赖不同的签名生成器、回调处理器。
Factory 负责根据 payType 字符串返回具体的 PaymentStrategy 实现Strategy 接口定义 Pay() 和 Refund(),但不关心如何序列化请求或验签Signer),而这个 Signer 本身也可以是另一个 Strategy 或由 Builder 构建type PaymentStrategy interface {
Pay(order *Order) error
}
type WechatPayStrategy struct {
signer Signer // 可替换的签名策略
}
func NewWechatPayStrategy(signer Signer) *WechatPayStrategy {
return &WechatPayStrategy{signer: signer}
}
func PaymentStrategyFactory(payType string) PaymentStrategy {
switch payType {
case "wechat":
return NewWechatPayStrategy(&WechatSHA256Signer{})
case "alipay":
return NewAlipayStrategy(&AlipayRSA2Signer{})

default:
panic("unknown pay type")
}
}
常见错误是把事件监听器注册逻辑塞进单例的 GetInstance() 方法里,导致每次获取实例都重复绑定、内存泄漏、测试困难。Singleton 只应负责“唯一实例”的生命周期;Observer 关系必须可手动管理。
Logger)暴露 Subscribe() 和 Unsubscribe() 方法,而不是自动绑定main() 或模块 init() 中完成sync.Once 初始化单例,切勿在其中调用任何可能阻塞或依赖外部状态的 Observer 注册逻辑var loggerInstance *Logger
var loggerOnce sync.Once
func GetLogger() *Logger {
loggerOnce.Do(func() {
loggerInstance = &Logger{
subscribers: make(map[int]func(string)),
}
})
return loggerInstance
}
// 正确:由使用者决定何时订阅
func main() {
log := GetLogger()
log.Subscribe(1, func(msg string) { fmt.Println("[DEBUG]", msg) })
}
Go 没有继承,靠接口组合实现 Decorator,但过度嵌套会导致方法爆炸和 nil panic。典型场景是给 HTTP handler 加日志、熔断、指标上报——每层 Decorator 都要完整实现 http.Handler 接口,哪怕只改一个方法。
func(http.Handler) http.Handler)而非结构体next http.Handler)在构造时非 nil,并在 ServeHTTP 开头加 if d.next == nil { panic(...) }
func WithMetrics(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 记录指标
next.ServeHTTP(w, r) // 注意:这里假设 next 不为 nil
})
}
func main() {
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
})
// 正确顺序:指标 → 熔断 → 日志 → 原始 handler
http.ListenAndServe(":8080", WithMetrics(WithCircuitBreaker(WithLogging(h))))
}
Builder 适合构建复杂对象,但一旦和其他模式混用,容易变成“配置黑洞”。比如用 Builder 构建一个带多种 Strategy 的 Service 实例,最后却把所有策略类型、超时、重试次数全塞进 Builder 的链式调用里,导致可读性崩坏。
Build() 方法不应做 I/O 或阻塞操作(比如读配置文件、连数据库),否则单元测试无法 mock复杂点往往不在模式本身,而在谁持有控制权——是 Builder 决定用哪个 Strategy,还是上层代码传进去。后者更可控,也更容易测试。