How would you implement the decorator, strategy, and adapter patterns idiomatically in Go?
Question 558MediumGo 1.22 to 1.25
In Go these patterns usually collapse into small interfaces plus function types, with no class hierarchies.
// Strategy: behavior chosen at runtime through an interface or a func.
type PricingStrategy interface{ Price(base float64) float64 }
type Discount struct{ Pct float64 }
func (d Discount) Price(b float64) float64 { return b * (1 - d.Pct) }
type Checkout struct{ Pricing PricingStrategy }
// Adapter: http.HandlerFunc adapts a plain func to the http.Handler interface.
type HandlerFunc func(http.ResponseWriter, *http.Request)
func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) { f(w, r) }
// Decorator: wrap an interface with the same interface, adding behavior.
type Store interface {
Get(ctx context.Context, key string) ([]byte, error)
}
type metricsStore struct {
next Store
hist *prometheus.HistogramVec
}
func (m metricsStore) Get(ctx context.Context, key string) ([]byte, error) {
start := time.Now()
v, err := m.next.Get(ctx, key)
m.hist.WithLabelValues(strconv.FormatBool(err == nil)).Observe(time.Since(start).Seconds())
return v, err
}
// Middleware chain (decorators composed)
func Chain(h http.Handler, mw ...func(http.Handler) http.Handler) http.Handler {
for _, m := range slices.Backward(mw) {
h = m(h)
}
return h
}
Key points: a one-method strategy is often just a func type (sort.Slice's less, strings.Map). The "func type with a method" trick is the canonical Go adapter. Decorators work cleanly because interfaces are small and satisfied implicitly. Gotcha: a decorator that wraps http.ResponseWriter hides optional interfaces (http.Flusher, http.Hijacker). Implement Unwrap() http.ResponseWriter so http.ResponseController (Go 1.20) can reach them.
More on Go Idioms, Design Patterns & Language Design
- Q556How do you structure a large Go project (cmd/, internal/, pkg/ debate)? How should packages be named and split to avoid stutter and cycles?
- Q557How do you do dependency injection in Go? Compare manual constructor wiring with google/wire and uber/fx.
- Q559How do you implement a circuit breaker and bulkhead in Go? What state machine and concurrency concerns are involved?
- Q560What are the uses of the blank identifier (_)? Explain import _, var _ = , _ = x and blank struct fields.
- Q561What are anonymous structs and struct tags? When would you use an anonymous struct in tests or JSON handling?
- Q562How do you design an idempotent HTTP API handler in Go (idempotency keys, storage, concurrency)?