Go

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

All 16 Go Idioms, Design Patterns & Language Design questions