Behavioral Complexity: Low

Strategy in Go

Encapsulate interchangeable algorithms behind one interface so callers can swap behavior without branching on concrete rules.

The Problem

Business rules such as pricing, tax, ranking, or retry policies often vary by context. If every caller uses conditionals to choose which algorithm to run, the policy logic spreads out and becomes harder to test or replace.

The Solution

Strategy hides each algorithm behind a shared interface. In Go, strategies are often tiny interfaces backed by small structs or even function adapters. The caller holds one strategy value and delegates the variable behavior through that contract.

Structure

  • Strategy interface: PricingStrategy defines how cart totals are calculated.
  • Concrete strategies: Standard, member, and campaign pricing apply different rules.
  • Context: Checkout delegates the calculation to the selected strategy.
  • Client: Chooses the strategy based on runtime context.

Implementation

This example calculates a cart total through interchangeable pricing strategies. The checkout flow does not branch on membership or campaign rules once a strategy has been selected.

package main

type CartLine struct {
	Name      string
	Qty       int
	UnitPrice int
}

type PricingStrategy interface {
	Total(lines []CartLine) int
}

type Checkout struct {
	strategy PricingStrategy
}

func NewCheckout(strategy PricingStrategy) Checkout {
	return Checkout{strategy: strategy}
}

func (c Checkout) Total(lines []CartLine) int {
	return c.strategy.Total(lines)
}

Best Practices

  • Keep strategy interfaces very small so new policies stay cheap to implement.
  • Inject the strategy from the outside; avoid contexts that secretly choose their own policy.
  • Use ordinary functions when a full struct adds no value. In Go, function types can be perfectly good strategies.
  • Prefer Strategy when callers need to swap algorithms at runtime.
  • Do not create a strategy hierarchy for logic that rarely changes or has only one implementation.

When to Use

  • You have several interchangeable algorithms with the same input and output shape.
  • Callers should choose policy without long conditional branches.
  • Different environments or customer tiers need different rules.

When NOT to Use

  • The logic is stable and a simple helper function is enough.
  • There is only one algorithm and no realistic need to swap it.
  • The abstraction would add more indirection than flexibility.