go-design-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGo 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 → 运行时panicReturn 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 repogo
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, rangego
// ✅ 推荐——sync.Mutex的零值是未锁定的互斥锁
var mu sync.Mutex
// ✅ 推荐——bytes.Buffer的零值是空缓冲区
var buf bytes.Buffer
// ✅ 推荐——切片的零值是有效的空切片
var users []User // nil切片可正常使用append、len、rangeUse 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
验证检查清单
- Functional options used for types with optional configuration
- Constructors validate required dependencies and return errors
- Factory functions return interfaces, not concrete types
- No god interfaces — each interface has 1-3 methods
- Middleware follows signature
func(http.Handler) http.Handler - Decorators wrap interfaces, not concrete types
- used for all resource cleanup (files, connections, locks)
defer - Zero values are meaningful — no unnecessary initialization
- No premature abstractions — interfaces extracted only when needed
- Composition used instead of embedding for code reuse
- 对带有可选配置的类型使用函数选项模式
- 构造器验证必填依赖并返回错误
- 工厂函数返回接口而非具体类型
- 无上帝接口——每个接口包含1-3个方法
- 中间件遵循签名
func(http.Handler) http.Handler - 装饰器包装接口而非具体类型
- 所有资源清理(文件、连接、锁)都使用
defer - 零值具备实际意义——无不必要的初始化
- 无过早抽象——仅在需要时提取接口
- 使用组合而非嵌入实现代码复用