Golang常见设计模式的实际应用_Golang设计模式实战总结

2次阅读

go中单例慎用sync.Once而非包级变量,因后者init时执行不可控,而前者支持懒加载与错误处理;工厂模式应避免类型断言,宜用选项函数组合;装饰器在Go中实为函数式链式调用;观察者模式多被channel替代以解耦并发

Golang常见设计模式的实际应用_Golang设计模式实战总结

为什么 Go 里单例模式要慎用 sync.Once 而不是包级变量?

Go 的包级变量看似天然单例,但若初始化逻辑含副作用(如打开文件、连接数据库),就可能在测试或热重载时重复执行——因为包初始化只在 init() 阶段跑一次,无法控制时机或重试。而 sync.Once 把“首次调用”和“初始化动作”绑定到具体函数上,更可控。

  • 包级变量适合无副作用的常量型单例(如 var defaultClient = &http.Client{}
  • 有依赖或需错误处理的场景,必须用 sync.Once + 懒加载结构体字段,例如:
type DB struct {     db  *sql.DB     once sync.Once     err error }  func (d *DB) Instance() (*sql.DB, error) {     d.once.Do(func() {         d.db, d.err = sql.Open("mysql", "user:pass@/db")     })     return d.db, d.err }

注意:sync.Once 不处理初始化失败后的重试,出错后后续调用仍返回相同错误——这是设计使然,不是 bug

工厂模式在 HTTP 中间件注册时怎么避免类型断言?

直接写 func NewAuthMiddleware() Middleware 看似简单,但当中间件需要配置(如 JWT 密钥、超时时间)时,硬编码参数会让测试难 mock,也违背开闭原则。更实用的做法是定义工厂接口 + 选项函数:

  • 用函数类型封装构造逻辑:type MiddlewareFactory func(http.Handler) http.Handler
  • 用选项模式传参,避免长参数列表:func WithTimeout(d time.Duration) Option
  • 注册时统一走工厂链:mux.Use(authFactory(WithSecret(key), WithTimeout(30*time.Second)))

这样既保持类型安全(不靠 Interface{} + 断言),又支持组合与替换。别把工厂写成巨型 switch,按业务域拆分小工厂更易维护。

立即学习go语言免费学习笔记(深入)”;

装饰器模式(Decorator)和中间件有什么本质区别

Go 里常把 func(http.Handler) http.Handler 叫装饰器,但它其实是函数式装饰器,不是经典 OOP 装饰器。关键差异在于:

  • 经典装饰器持有被装饰对象引用并委托调用;Go 中间件通常不持有 handler 实例,而是返回新 handler 闭包
  • Go 的装饰链是编译期静态拼接(mw1(mw2(handler))),而 OOP 装饰器可运行时动态增删
  • 真正需要运行时插拔时,别硬套装饰器,改用策略模式 + 注册表(如 map[String]func(http.Handler) http.Handler

所以别纠结“是不是正宗装饰器”,重点看是否满足:职责单一、可组合、不侵入业务 handler 逻辑。

为什么观察者模式在 Go 里多数时候该换成 channel?

手写 Subscribe/Notify 接口容易陷入锁竞争或 goroutine 泄漏。比如用 map 存 listener 切片再遍历通知,一旦某个 listener 阻塞,整个通知就卡住。

  • 轻量事件(如状态变更通知)直接用无缓冲 channel:ch := make(chan Event),让监听方自己起 goroutine range
  • 需广播且容忍丢失时,用带缓冲 channel + select default 避免阻塞
  • 真需要回调管理(如注销监听)才考虑封装,但底层仍用 channel 转发,而非同步遍历

channel 天然解耦生产者与消费者节奏,比手写观察者更符合 Go 的并发哲学——别用模式套问题,先想“谁该负责接收、谁该负责处理、数据流怎么自然断开”。

text=ZqhQzanResources