Go 设计模式实战——在 Go 中实现经典设计模式的正确姿势
Go 设计模式实战——在 Go 中实现经典设计模式的正确姿势
适读人群:有 Java 设计模式基础、想在 Go 中正确落地的工程师 | 阅读时长:约17分钟 | 核心价值:不照搬 Java 写法,理解 Go 惯用的模式实现
我把 Java 设计模式直接搬到 Go 里的教训
刚转 Go 的时候,我最喜欢用的就是 Strategy 模式和 Factory 模式——Java 里用得滚瓜烂熟,想着 Go 应该也差不多。
于是我写出了这样的代码:
// 抽象接口(模仿 Java 抽象类)
type PaymentProcessor interface {
Process(amount float64) error
GetName() string
Validate() bool
GetFee(amount float64) float64
}
// 具体实现
type AlipayProcessor struct {
config AlipayConfig
}
func (a *AlipayProcessor) Process(amount float64) error { ... }
func (a *AlipayProcessor) GetName() string { return "Alipay" }
func (a *AlipayProcessor) Validate() bool { ... }
func (a *AlipayProcessor) GetFee(amount float64) float64 { ... }
// 工厂类
type PaymentProcessorFactory struct{}
func (f *PaymentProcessorFactory) Create(typ string) PaymentProcessor { ... }代码没什么问题,能跑。但老同事看完笑了:"你这个接口里有 4 个方法,Go 的接口应该尽量小,最好只有 1 个方法。你的工厂类完全没必要,一个 NewXxxProcessor 函数就行了。"
那次之后我专门研究了 Go 的设计模式"正确姿势",发现和 Java 的确有很大不同。
Go 设计模式的核心原则
在学具体模式之前,先记住 Go 的设计哲学:
- 接口尽量小:Go 的
io.Reader只有一个方法,io.Writer也只有一个方法,这是最地道的 Go - 组合优于继承:Go 没有类继承,用结构体嵌套和接口组合代替
- 函数也是值:很多 Java 里需要接口 + 类来实现的模式,Go 里直接用函数就够了
- 不要为了模式而模式:Go 标准库的代码简单直接,不会看到大量设计模式
策略模式(Strategy)
Java 写法:定义接口,多个类实现接口,注入不同实现切换策略。
Go 的方式:函数就是策略。
package strategy
import (
"fmt"
"sort"
)
// Java 风格(不推荐):
// type SortStrategy interface { Sort(data []int) }
// type BubbleSort struct{} func (b *BubbleSort) Sort(...) {...}
// Go 风格:直接用函数类型
type SortFunc func(data []int)
func BubbleSort(data []int) {
n := len(data)
for i := 0; i < n-1; i++ {
for j := 0; j < n-1-i; j++ {
if data[j] > data[j+1] {
data[j], data[j+1] = data[j+1], data[j]
}
}
}
}
func QuickSort(data []int) {
sort.Ints(data) // 标准库快排
}
// 使用函数作为策略
type Sorter struct {
strategy SortFunc
}
func NewSorter(strategy SortFunc) *Sorter {
return &Sorter{strategy: strategy}
}
func (s *Sorter) Sort(data []int) {
s.strategy(data)
}
// 更复杂的策略——带状态,才需要接口
type PriceStrategy interface {
Calculate(basePrice float64) float64
}
type DiscountStrategy struct {
Discount float64 // 折扣率
MinOrder float64 // 最小订单金额
}
func (d DiscountStrategy) Calculate(basePrice float64) float64 {
if basePrice < d.MinOrder {
return basePrice
}
return basePrice * d.Discount
}
type VIPStrategy struct {
Level int
}
func (v VIPStrategy) Calculate(basePrice float64) float64 {
discount := 1.0 - float64(v.Level)*0.05
return basePrice * discount
}
func CalculatePrice(base float64, strategy PriceStrategy) float64 {
return strategy.Calculate(base)
}
func StrategyExample() {
// 无状态策略 → 直接传函数
sorter := NewSorter(BubbleSort)
data := []int{5, 3, 1, 4, 2}
sorter.Sort(data)
fmt.Println(data)
// 有状态策略 → 接口
price := CalculatePrice(100.0, DiscountStrategy{Discount: 0.8, MinOrder: 50})
fmt.Printf("折扣后: %.2f\n", price) // 80.00
vipPrice := CalculatePrice(100.0, VIPStrategy{Level: 3})
fmt.Printf("VIP3 价格: %.2f\n", vipPrice) // 85.00
}装饰器模式(Decorator)
这是 Go 里最常用的模式,HTTP 中间件就是典型的装饰器。
package decorator
import (
"fmt"
"log"
"net/http"
"time"
)
// HTTP 处理器的装饰器
type Middleware func(http.Handler) http.Handler
// 日志中间件
func WithLogging(logger *log.Logger) Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
logger.Printf("→ %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
logger.Printf("← %s %s (%v)", r.Method, r.URL.Path, time.Since(start))
})
}
}
// 认证中间件
func WithAuth(token string) Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Authorization") != "Bearer "+token {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
}
// CORS 中间件
func WithCORS(allowedOrigins ...string) Middleware {
allowedMap := make(map[string]bool)
for _, o := range allowedOrigins {
allowedMap[o] = true
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("Origin")
if allowedMap[origin] || len(allowedOrigins) == 0 {
w.Header().Set("Access-Control-Allow-Origin", origin)
}
next.ServeHTTP(w, r)
})
}
}
// Chain 组合多个中间件
func Chain(middlewares ...Middleware) Middleware {
return func(final http.Handler) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
final = middlewares[i](final)
}
return final
}
}
func DecoratorExample() {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello!")
})
logger := log.Default()
chain := Chain(
WithCORS("https://example.com"),
WithLogging(logger),
WithAuth("my-secret-token"),
)
http.Handle("/api", chain(handler))
}观察者模式(Observer)
package observer
import (
"fmt"
"sync"
)
// Event 是事件类型
type Event[T any] struct {
Topic string
Payload T
}
// Handler 是事件处理函数
type Handler[T any] func(Event[T])
// EventBus 是一个简单的事件总线
type EventBus[T any] struct {
mu sync.RWMutex
handlers map[string][]Handler[T]
}
func NewEventBus[T any]() *EventBus[T] {
return &EventBus[T]{
handlers: make(map[string][]Handler[T]),
}
}
func (eb *EventBus[T]) Subscribe(topic string, handler Handler[T]) {
eb.mu.Lock()
defer eb.mu.Unlock()
eb.handlers[topic] = append(eb.handlers[topic], handler)
}
func (eb *EventBus[T]) Publish(event Event[T]) {
eb.mu.RLock()
handlers := eb.handlers[event.Topic]
eb.mu.RUnlock()
for _, h := range handlers {
h(event) // 同步调用
}
}
func (eb *EventBus[T]) PublishAsync(event Event[T]) {
eb.mu.RLock()
handlers := eb.handlers[event.Topic]
eb.mu.RUnlock()
for _, h := range handlers {
h := h // 捕获(Go 1.22 前需要)
go h(event) // 异步调用
}
}
// 领域事件示例
type OrderEvent struct {
OrderID string
UserID string
Amount float64
Status string
}
func ObserverExample() {
bus := NewEventBus[OrderEvent]()
// 邮件通知处理器
bus.Subscribe("order.created", func(e Event[OrderEvent]) {
fmt.Printf("[EMAIL] 给用户 %s 发送订单确认邮件,订单号: %s\n",
e.Payload.UserID, e.Payload.OrderID)
})
// 库存扣减处理器
bus.Subscribe("order.created", func(e Event[OrderEvent]) {
fmt.Printf("[INVENTORY] 扣减订单 %s 的库存\n", e.Payload.OrderID)
})
// 积分处理器
bus.Subscribe("order.paid", func(e Event[OrderEvent]) {
points := int(e.Payload.Amount / 10)
fmt.Printf("[POINTS] 用户 %s 获得 %d 积分\n", e.Payload.UserID, points)
})
// 发布事件
bus.Publish(Event[OrderEvent]{
Topic: "order.created",
Payload: OrderEvent{
OrderID: "ORD-001",
UserID: "USER-123",
Amount: 299.0,
Status: "created",
},
})
}单例模式(Singleton)
Go 里单例有标准写法:
package singleton
import (
"database/sql"
"sync"
)
// 错误写法:不是线程安全的
var db *sql.DB
func GetDB() *sql.DB {
if db == nil {
db, _ = sql.Open("mysql", "...")
}
return db
}
// 正确写法一:sync.Once(推荐)
var (
dbInstance *sql.DB
once sync.Once
)
func GetDBOnce() *sql.DB {
once.Do(func() {
var err error
dbInstance, err = sql.Open("mysql", "root:password@tcp(localhost:3306)/mydb")
if err != nil {
panic(err)
}
})
return dbInstance
}
// 正确写法二:init 函数(适合程序启动时就确定的单例)
var configInstance *AppConfig
func init() {
configInstance = loadConfig()
}
func GetConfig() *AppConfig {
return configInstance
}
type AppConfig struct {
Debug bool
Env string
}
func loadConfig() *AppConfig {
return &AppConfig{Env: "production"}
}三个踩坑实录
坑一:接口定义太大,导致测试困难
现象:某个接口有 15 个方法,写单元测试时需要实现所有 15 个方法,即使大部分测试只用到其中 2 个。
原因:从 Java 转来的习惯,喜欢把相关的方法都放在一个"服务"接口里。
解法:按照 Go 的接口隔离原则,把大接口拆分为多个小接口,每个接口只有 1-3 个方法。需要组合时,可以在函数参数里声明匿名接口:
// 不要这样
type UserService interface {
Get(id string) (*User, error)
Create(user *User) error
Update(user *User) error
Delete(id string) error
List(filter UserFilter) ([]*User, error)
// ... 更多方法
}
// 这样更好
type UserGetter interface { Get(id string) (*User, error) }
type UserCreator interface { Create(user *User) error }
// 用到的地方声明需要的最小接口
func sendWelcomeEmail(getter UserGetter, id string) error {
user, err := getter.Get(id)
// ...
}坑二:用 goroutine 实现观察者时内存泄漏
现象:发布了 10 万个事件,goroutine 数量暴增,最终 OOM。
原因:PublishAsync 里每次 go h(event) 都创建一个新 goroutine,如果 handler 处理慢(比如调用下游服务),goroutine 堆积。
解法:用 channel + 工作池来处理异步事件,而不是无限制创建 goroutine。
坑三:sync.Once 里的 panic 导致 Once 永远不执行
现象:数据库连接配置错误,once.Do 里 panic 了,被外层 recover 捕获后,再次调用 GetDB() 时,once.Do 里的函数不再执行,永远返回 nil。
原因:sync.Once 的语义是"只执行一次",无论成功还是 panic,执行过就不再执行。
解法:在 once.Do 里不要 panic,改为返回 error 并检查:
var (
dbInstance *sql.DB
dbInitErr error
once sync.Once
)
func GetDB() (*sql.DB, error) {
once.Do(func() {
dbInstance, dbInitErr = sql.Open("mysql", "...")
if dbInitErr == nil {
dbInitErr = dbInstance.Ping()
}
})
return dbInstance, dbInitErr
}Java 对比
Java 的设计模式书(GoF 23 种)是基于 Java 的面向对象体系写的。很多模式(比如 Abstract Factory、Template Method)在 Go 里根本不需要——因为 Go 用函数和接口就能更简洁地实现。
但也有些模式在 Go 里非常自然:
- Decorator(装饰器)= HTTP 中间件,Go 里几乎人人在用
- Strategy(策略)= 传函数参数,比 Java 更简洁
- Iterator(迭代器)= for range,语言级别支持(Go 1.23 还加了 iter 包)
- Observer(观察者)= channel + goroutine,天然支持
小结
在 Go 里实现设计模式的要点:
- 接口要小:1-3 个方法最佳,不要把所有相关功能堆在一个接口里
- 函数是一等公民:无状态的策略直接传函数,不需要接口+类
- 组合代替继承:用结构体嵌套实现代码复用
- 单例用 sync.Once:线程安全,简洁
- 中间件就是装饰器:这是 Go 里最常用的模式
