go-design-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Go Design Patterns

Go 设计模式

Go favors composition over inheritance and simplicity over abstraction. These patterns are idiomatic Go — not Java patterns ported to Go.
Go 偏好组合而非继承,偏好简洁而非过度抽象。这些模式是Go语言的惯用实现——并非从Java移植过来的模式。

1. Functional Options

1. 函数选项模式

The most idiomatic Go pattern for configurable constructors. Use when a type has many optional settings.
go
type Server struct {
    addr         string
    readTimeout  time.Duration
    writeTimeout time.Duration
    logger       *slog.Logger
}

type Option func(*Server)

func WithAddr(addr string) Option {
    return func(s *Server) {
        s.addr = addr
    }
}

func WithReadTimeout(d time.Duration) Option {
    return func(s *Server) {
        s.readTimeout = d
    }
}

func WithLogger(l *slog.Logger) Option {
    return func(s *Server) {
        s.logger = l
    }
}

func NewServer(opts ...Option) *Server {
    s := &Server{
        addr:         ":8080",       // sensible defaults
        readTimeout:  5 * time.Second,
        writeTimeout: 10 * time.Second,
        logger:       slog.Default(),
    }
    for _, opt := range opts {
        opt(s)
    }
    return s
}

// Usage:
srv := NewServer(
    WithAddr(":9090"),
    WithReadTimeout(10*time.Second),
)
这是Go语言中用于可配置构造函数的最惯用模式。当某个类型拥有多个可选配置时使用。
go
type Server struct {
    addr         string
    readTimeout  time.Duration
    writeTimeout time.Duration
    logger       *slog.Logger
}

type Option func(*Server)

func WithAddr(addr string) Option {
    return func(s *Server) {
        s.addr = addr
    }
}

func WithReadTimeout(d time.Duration) Option {
    return func(s *Server) {
        s.readTimeout = d
    }
}

func WithLogger(l *slog.Logger) Option {
    return func(s *Server) {
        s.logger = l
    }
}

func NewServer(opts ...Option) *Server {
    s := &Server{
        addr:         ":8080",       // 合理默认值
        readTimeout:  5 * time.Second,
        writeTimeout: 10 * time.Second,
        logger:       slog.Default(),
    }
    for _, opt := range opts {
        opt(s)
    }
    return s
}

// 使用示例:
srv := NewServer(
    WithAddr(":9090"),
    WithReadTimeout(10*time.Second),
)

When to use functional options vs config struct:

函数选项模式 vs 配置结构体的适用场景:

go
// Use functional options when:
// - Many optional parameters with sensible defaults
// - API evolves over time (new options don't break callers)
// - Options need validation or side effects

// Use config struct when:
// - Most fields are required
// - Configuration is loaded from file/env (easy to deserialize)
// - No need for default values
type Config struct {
    Addr     string        `yaml:"addr"`
    DBUrl    string        `yaml:"db_url"`
    LogLevel slog.Level    `yaml:"log_level"`
}
go
// 使用函数选项模式的场景:
// - 存在大量可选参数且有合理默认值
// - API会随时间演进(新增选项不会破坏现有调用方)
// - 选项需要验证或产生副作用

// 使用配置结构体的场景:
// - 大多数字段为必填项
// - 配置从文件/环境变量加载(易于反序列化)
// - 不需要默认值
type Config struct {
    Addr     string        `yaml:"addr"`
    DBUrl    string        `yaml:"db_url"`
    LogLevel slog.Level    `yaml:"log_level"`
}

2. Constructor Pattern

2. 构造器模式

Every exported type with invariants needs a constructor.
go
// ✅ Good — constructor enforces invariants
func NewUserService(repo UserRepository, logger *slog.Logger) (*UserService, error) {
    if repo == nil {
        return nil, errors.New("user service: nil repository")
    }
    if logger == nil {
        return nil, errors.New("user service: nil logger")
    }
    return &UserService{repo: repo, logger: logger}, nil
}

// ❌ Bad — struct literal with no validation
svc := &UserService{} // nil dependencies → panic at runtime
每个带有约束条件的导出类型都需要一个构造器。
go
// ✅ 推荐——构造器强制执行约束条件
func NewUserService(repo UserRepository, logger *slog.Logger) (*UserService, error) {
    if repo == nil {
        return nil, errors.New("user service: nil repository")
    }
    if logger == nil {
        return nil, errors.New("user service: nil logger")
    }
    return &UserService{repo: repo, logger: logger}, nil
}

// ❌ 不推荐——无验证的结构体字面量
vc := &UserService{} // 依赖为nil → 运行时panic

Return error from constructor when validation is needed:

当需要验证时,从构造器返回错误:

go
// ✅ Good — constructor returns error
func NewEmailAddress(raw string) (EmailAddress, error) {
    if !isValidEmail(raw) {
        return EmailAddress{}, fmt.Errorf("invalid email: %s", raw)
    }
    return EmailAddress{value: raw}, nil
}
go
// ✅ 推荐——构造器返回错误
func NewEmailAddress(raw string) (EmailAddress, error) {
    if !isValidEmail(raw) {
        return EmailAddress{}, fmt.Errorf("invalid email: %s", raw)
    }
    return EmailAddress{value: raw}, nil
}

3. Factory Pattern

3. 工厂模式

Use when you need to create different implementations of an interface based on runtime configuration.
go
type Store interface {
    Get(ctx context.Context, key string) (string, error)
    Set(ctx context.Context, key, value string) error
}

func NewStore(cfg Config) (Store, error) {
    switch cfg.StoreType {
    case "redis":
        return newRedisStore(cfg.RedisAddr)
    case "memory":
        return newMemoryStore(), nil
    case "postgres":
        return newPostgresStore(cfg.DatabaseURL)
    default:
        return nil, fmt.Errorf("unknown store type: %s", cfg.StoreType)
    }
}
Return the interface, not a concrete type. The factory is the only place that knows about concrete implementations.
当需要根据运行时配置创建接口的不同实现时使用。
go
type Store interface {
    Get(ctx context.Context, key string) (string, error)
    Set(ctx context.Context, key, value string) error
}

func NewStore(cfg Config) (Store, error) {
    switch cfg.StoreType {
    case "redis":
        return newRedisStore(cfg.RedisAddr)
    case "memory":
        return newMemoryStore(), nil
    case "postgres":
        return newPostgresStore(cfg.DatabaseURL)
    default:
        return nil, fmt.Errorf("unknown store type: %s", cfg.StoreType)
    }
}
返回接口而非具体类型。工厂是唯一知晓具体实现的地方。

4. Strategy Pattern

4. 策略模式

Swap behavior at runtime by injecting function types or interfaces.
通过注入函数类型或接口在运行时切换行为。

With function types (simpler):

使用函数类型(更简洁):

go
type RetryStrategy func(attempt int) time.Duration

func ExponentialBackoff(base time.Duration) RetryStrategy {
    return func(attempt int) time.Duration {
        return base * time.Duration(1<<uint(attempt))
    }
}

func ConstantDelay(d time.Duration) RetryStrategy {
    return func(_ int) time.Duration {
        return d
    }
}

func Retry(ctx context.Context, maxAttempts int, strategy RetryStrategy, fn func() error) error {
    var err error
    for i := 0; i < maxAttempts; i++ {
        if err = fn(); err == nil {
            return nil
        }
        select {
        case <-ctx.Done():
            return ctx.Err()
        case <-time.After(strategy(i)):
        }
    }
    return fmt.Errorf("after %d attempts: %w", maxAttempts, err)
}
go
type RetryStrategy func(attempt int) time.Duration

func ExponentialBackoff(base time.Duration) RetryStrategy {
    return func(attempt int) time.Duration {
        return base * time.Duration(1<<uint(attempt))
    }
}

func ConstantDelay(d time.Duration) RetryStrategy {
    return func(_ int) time.Duration {
        return d
    }
}

func Retry(ctx context.Context, maxAttempts int, strategy RetryStrategy, fn func() error) error {
    var err error
    for i := 0; i < maxAttempts; i++ {
        if err = fn(); err == nil {
            return nil
        }
        select {
        case <-ctx.Done():
            return ctx.Err()
        case <-time.After(strategy(i)):
        }
    }
    return fmt.Errorf("after %d attempts: %w", maxAttempts, err)
}

With interfaces (when behavior is complex):

使用接口(当行为复杂时):

go
type Notifier interface {
    Notify(ctx context.Context, event Event) error
}

type SlackNotifier struct { webhookURL string }
type EmailNotifier struct { smtpClient *smtp.Client }
type NoopNotifier  struct{}

// Each implements Notifier. Inject the right one at startup.
go
type Notifier interface {
    Notify(ctx context.Context, event Event) error
}

type SlackNotifier struct { webhookURL string }
type EmailNotifier struct { smtpClient *smtp.Client }
type NoopNotifier  struct{}

// 每个结构体都实现Notifier接口。在启动时注入合适的实现。

5. Middleware / Decorator Pattern

5. 中间件/装饰器模式

Wrap behavior around a core function or interface.
在核心函数或接口周围包装行为。

HTTP middleware (standard pattern):

HTTP中间件(标准模式):

go
type Middleware func(http.Handler) http.Handler

func Chain(handler http.Handler, middlewares ...Middleware) http.Handler {
    for i := len(middlewares) - 1; i >= 0; i-- {
        handler = middlewares[i](handler)
    }
    return handler
}

// Usage:
handler := Chain(appHandler, Recoverer, RequestID, Logger, Auth)
go
type Middleware func(http.Handler) http.Handler

func Chain(handler http.Handler, middlewares ...Middleware) http.Handler {
    for i := len(middlewares) - 1; i >= 0; i-- {
        handler = middlewares[i](handler)
    }
    return handler
}

// 使用示例:
handler := Chain(appHandler, Recoverer, RequestID, Logger, Auth)

Interface decorator:

接口装饰器:

go
type UserRepository interface {
    GetByID(ctx context.Context, id string) (*User, error)
}

// Logging decorator
type loggingUserRepo struct {
    next   UserRepository
    logger *slog.Logger
}

func NewLoggingUserRepo(next UserRepository, logger *slog.Logger) UserRepository {
    return &loggingUserRepo{next: next, logger: logger}
}

func (r *loggingUserRepo) GetByID(ctx context.Context, id string) (*User, error) {
    start := time.Now()
    user, err := r.next.GetByID(ctx, id)
    r.logger.Info("GetByID",
        slog.String("id", id),
        slog.Duration("duration", time.Since(start)),
        slog.Any("error", err),
    )
    return user, err
}
Stack decorators:
cache → logging → metrics → actual repo
.
go
type UserRepository interface {
    GetByID(ctx context.Context, id string) (*User, error)
}

// 日志装饰器
type loggingUserRepo struct {
    next   UserRepository
    logger *slog.Logger
}

func NewLoggingUserRepo(next UserRepository, logger *slog.Logger) UserRepository {
    return &loggingUserRepo{next: next, logger: logger}
}

func (r *loggingUserRepo) GetByID(ctx context.Context, id string) (*User, error) {
    start := time.Now()
    user, err := r.next.GetByID(ctx, id)
    r.logger.Info("GetByID",
        slog.String("id", id),
        slog.Duration("duration", time.Since(start)),
        slog.Any("error", err),
    )
    return user, err
}
装饰器堆叠顺序:
缓存 → 日志 → 指标 → 实际仓库

6. Result Type Pattern

6. 结果类型模式

For operations that can return a value or an error in concurrent pipelines:
go
type Result[T any] struct {
    Value T
    Err   error
}

func fetchAll(ctx context.Context, ids []string) []Result[User] {
    results := make([]Result[User], len(ids))
    var wg sync.WaitGroup

    for i, id := range ids {
        wg.Add(1)
        go func(i int, id string) {
            defer wg.Done()
            user, err := fetchUser(ctx, id)
            results[i] = Result[User]{Value: user, Err: err}
        }(i, id)
    }

    wg.Wait()
    return results
}
用于在并发流水线中可能返回值或错误的操作:
go
type Result[T any] struct {
    Value T
    Err   error
}

func fetchAll(ctx context.Context, ids []string) []Result[User] {
    results := make([]Result[User], len(ids))
    var wg sync.WaitGroup

    for i, id := range ids {
        wg.Add(1)
        go func(i int, id string) {
            defer wg.Done()
            user, err := fetchUser(ctx, id)
            results[i] = Result[User]{Value: user, Err: err}
        }(i, id)
    }

    wg.Wait()
    return results
}

7. Cleanup with defer

7. 使用defer进行清理

Resource management pattern:

资源管理模式:

go
func processFile(path string) error {
    f, err := os.Open(path)
    if err != nil {
        return fmt.Errorf("open %s: %w", path, err)
    }
    defer f.Close()

    // process file...
    return nil
}
go
func processFile(path string) error {
    f, err := os.Open(path)
    if err != nil {
        return fmt.Errorf("open %s: %w", path, err)
    }
    defer f.Close()

    // 处理文件...
    return nil
}

Multi-resource cleanup:

多资源清理:

go
func migrate(ctx context.Context, srcDSN, dstDSN string) error {
    src, err := sql.Open("postgres", srcDSN)
    if err != nil {
        return fmt.Errorf("open source: %w", err)
    }
    defer src.Close()

    dst, err := sql.Open("postgres", dstDSN)
    if err != nil {
        return fmt.Errorf("open dest: %w", err)
    }
    defer dst.Close()

    // defers execute LIFO: dst.Close() first, then src.Close()
    return doMigration(ctx, src, dst)
}
go
func migrate(ctx context.Context, srcDSN, dstDSN string) error {
    src, err := sql.Open("postgres", srcDSN)
    if err != nil {
        return fmt.Errorf("open source: %w", err)
    }
    defer src.Close()

    dst, err := sql.Open("postgres", dstDSN)
    if err != nil {
        return fmt.Errorf("open dest: %w", err)
    }
    defer dst.Close()

    // defer遵循后进先出执行顺序:先执行dst.Close(),再执行src.Close()
    return doMigration(ctx, src, dst)
}

8. Sentinel Values vs Zero Values

8. 哨兵值 vs 零值

Use the zero value as a useful default when possible:

尽可能将零值用作有用的默认值:

go
// ✅ Good — sync.Mutex zero value is an unlocked mutex
var mu sync.Mutex

// ✅ Good — bytes.Buffer zero value is an empty buffer
var buf bytes.Buffer

// ✅ Good — slice zero value is a valid empty slice
var users []User // nil slice works with append, len, range
go
// ✅ 推荐——sync.Mutex的零值是未锁定的互斥锁
var mu sync.Mutex

// ✅ 推荐——bytes.Buffer的零值是空缓冲区
var buf bytes.Buffer

// ✅ 推荐——切片的零值是有效的空切片
var users []User // nil切片可正常使用append、len、range

Use sentinel values when zero value is ambiguous:

当零值存在歧义时使用哨兵值:

go
// When zero value is a valid input, use pointer or custom type
type Temperature struct {
    Celsius float64
    IsSet   bool
}

// Or use a pointer
func SetThreshold(t *float64) { // nil means "not configured"
    if t != nil {
        applyThreshold(*t)
    }
}
go
// 当零值是有效输入时,使用指针或自定义类型
type Temperature struct {
    Celsius float64
    IsSet   bool
}

// 或者使用指针
func SetThreshold(t *float64) { // nil表示「未配置」
    if t != nil {
        applyThreshold(*t)
    }
}

Anti-Patterns to Avoid

需要避免的反模式

go
// ❌ God interface — too many methods
type Service interface {
    GetUser(ctx context.Context, id string) (*User, error)
    CreateUser(ctx context.Context, u *User) error
    DeleteUser(ctx context.Context, id string) error
    ListOrders(ctx context.Context, userID string) ([]Order, error)
    // 20 more methods...
}
// → Split into focused interfaces: UserReader, UserWriter, OrderLister

// ❌ Premature abstraction — interface for one implementation
type UserCache interface {
    Get(key string) (*User, bool)
    Set(key string, user *User)
}
// If there's only ever one implementation, use the concrete type.
// Extract an interface when a second consumer or implementation appears.

// ❌ Java-style inheritance simulation
type BaseService struct { ... }
type UserService struct { BaseService }  // embedding is NOT inheritance
// → Use composition: UserService has a dependency, not a parent.
go
// ❌ 上帝接口——包含过多方法
type Service interface {
    GetUser(ctx context.Context, id string) (*User, error)
    CreateUser(ctx context.Context, u *User) error
    DeleteUser(ctx context.Context, id string) error
    ListOrders(ctx context.Context, userID string) ([]Order, error)
    // 还有20多个方法...
}
// → 拆分为专注的接口:UserReader、UserWriter、OrderLister

// ❌ 过早抽象——只为一个实现定义接口
type UserCache interface {
    Get(key string) (*User, bool)
    Set(key string, user *User)
}
// 如果永远只有一个实现,直接使用具体类型。当出现第二个消费者或实现时再提取接口。

// ❌ 模拟Java风格的继承
type BaseService struct { ... }
type UserService struct { BaseService }  // 嵌入并非继承
// → 使用组合:UserService包含一个依赖,而非拥有父类。

Verification Checklist

验证检查清单

  1. Functional options used for types with optional configuration
  2. Constructors validate required dependencies and return errors
  3. Factory functions return interfaces, not concrete types
  4. No god interfaces — each interface has 1-3 methods
  5. Middleware follows
    func(http.Handler) http.Handler
    signature
  6. Decorators wrap interfaces, not concrete types
  7. defer
    used for all resource cleanup (files, connections, locks)
  8. Zero values are meaningful — no unnecessary initialization
  9. No premature abstractions — interfaces extracted only when needed
  10. Composition used instead of embedding for code reuse
  1. 对带有可选配置的类型使用函数选项模式
  2. 构造器验证必填依赖并返回错误
  3. 工厂函数返回接口而非具体类型
  4. 无上帝接口——每个接口包含1-3个方法
  5. 中间件遵循
    func(http.Handler) http.Handler
    签名
  6. 装饰器包装接口而非具体类型
  7. 所有资源清理(文件、连接、锁)都使用
    defer
  8. 零值具备实际意义——无不必要的初始化
  9. 无过早抽象——仅在需要时提取接口
  10. 使用组合而非嵌入实现代码复用