🧭 Analogy
Getting to the airport, you pick a strategy: drive, take the train, or hail a cab. The goal is identical — only the algorithm changes. You choose based on time, budget and traffic, and you can switch on the day without changing your destination.
The problem
A type needs to perform a task that has several interchangeable implementations. The naive approach hard-codes them with a conditional:
func (d *Duck) Fly(kind string) {
switch kind {
case "wings":
// flap...
case "rocket":
// ignite...
case "none":
// can't fly
}
}
Every new behavior means editing Fly and growing the switch — the type is closed against extension but you keep opening it. Strategy pulls each algorithm into its own type behind a shared interface, so behavior becomes a value you set, not a branch you add.
Structure
classDiagram
class Duck {
-flyBehavior FlyBehavior
+PerformFly()
+SetFlyBehavior(FlyBehavior)
}
class FlyBehavior {
<<interface>>
+Fly()
}
class FlyWithWings { +Fly() }
class FlyNoWay { +Fly() }
Duck o--> FlyBehavior : delegates to
FlyBehavior <|.. FlyWithWings
FlyBehavior <|.. FlyNoWayThe Duck (the context) holds a FlyBehavior and delegates to it. Swapping the concrete behavior changes what the duck does — at runtime, without touching Duck.
How it works
sequenceDiagram participant C as Client participant D as Duck (context) participant F as FlyBehavior C->>D: SetFlyBehavior(FlyWithWings) C->>D: PerformFly() D->>F: Fly() F-->>D: "Flying with wings" C->>D: SetFlyBehavior(FlyNoWay) C->>D: PerformFly() D->>F: Fly() F-->>D: "Can't fly"
Idiomatic Go — the interface form
Each behavior is its own type satisfying a tiny interface, and the duck delegates. Edit and Run it:
package main
import "fmt"
// Strategy interface + two implementations
type FlyBehavior interface{ Fly() }
type FlyWithWings struct{}
func (FlyWithWings) Fly() { fmt.Println("Flying with wings") }
type FlyNoWay struct{}
func (FlyNoWay) Fly() { fmt.Println("Can't fly") }
// Context holds a strategy and delegates to it
type Duck struct{ fly FlyBehavior }
func (d *Duck) SetFly(fb FlyBehavior) { d.fly = fb }
func (d *Duck) PerformFly() { d.fly.Fly() }
func main() {
mallard := &Duck{fly: FlyWithWings{}}
mallard.PerformFly() // Flying with wings
mallard.SetFly(FlyNoWay{}) // swap behavior at runtime
mallard.PerformFly() // Can't fly
}
You can extend this with a QuackBehavior too, plus concrete MallardDuck and RubberDuck types that embed a base Duck — a great look at composition.
Idiomatic Go — the function form
Here’s where Go diverges from Java. When a strategy is a single operation, you don’t need an interface and a struct at all — a function value is the strategy:
type FlyFunc func()
type Duck struct{ fly FlyFunc }
func (d *Duck) PerformFly() { d.fly() }
mallard := &Duck{fly: func() { fmt.Println("Flying with wings") }}
mallard.PerformFly()
mallard.fly = func() { fmt.Println("Can't fly") } // swap in one line
🐹 Go reshapes this pattern
First-class functions collapse single-method Strategies into a func field or argument. Reach for the interface form when a strategy needs multiple methods or its own state; reach for the function form when it’s one operation. Both are Strategy.
The principles behind Strategy
Strategy is the Head First Chapter 1 pattern because it distils three of the book’s core principles into one idea:
- Encapsulate what varies — the fly/quack behavior is the changing part, so it’s pulled into its own interface, away from the stable
Duck. - Favor composition over inheritance — the duck has-a behavior rather than is-a subclass, so you mix and match at runtime instead of fixing it in a class tree.
- Program to interfaces, not implementations —
Duckdepends onFlyBehavior, never a concrete flier.
Its two close cousins are easy to mix up: State has the same shape but the object itself changes its behavior object as it transitions (and states often know each other), whereas a Strategy is chosen from outside and rarely changes itself. Template Method solves a similar “vary the behavior” problem with a fixed skeleton instead of a swappable whole.
In the standard library
sort.Slice(s, less)— you supply the comparison strategy as a function.http.HandlerFunc— an adapter that lets any function be a request-handling strategy.strings.Map(mapping, s)— the per-rune transformation is a pluggable strategy.template.FuncMap— inject formatting strategies into a template.
Pitfalls
⚠️ Don't over-engineer a single algorithm
If there’s truly only one implementation and no prospect of a second, an interface and a context type are ceremony with no payoff. Introduce Strategy when variation actually exists (or is clearly coming) — not speculatively.
When to use it — and when not
✅ Reach for it when
- You have several variants of an algorithm (sorting orders, pricing rules, compression schemes) and want to switch between them at runtime.
- You want to remove a sprawling if/else or switch that picks behavior.
- Callers should be able to plug in their own behavior without you editing the core type.
⛔ Think twice when
- There's only ever one algorithm and no realistic chance of a second — the indirection just adds noise.
- The 'strategies' differ by a single value, not behavior — a parameter is simpler than a type.
Related patterns
Let an object alter its behavior when its internal state changes, as if it changed class.
behavioralTemplate MethodDefine the skeleton of an algorithm once, deferring the steps that vary to per-type implementations.
behavioralCommandTurn a request into a standalone object, letting you parameterize, queue, log, and undo operations.
structuralDecoratorAttach new behavior to an object by wrapping it in another object of the same interface — without changing the original's type.
Check your understanding
Score: 0 / 51. In Go, what is the most idiomatic form of a simple Strategy?
When a strategy is a single operation, a func value is the cleanest expression — exactly what sort.Slice's less argument is.
2. How does Strategy differ from State?
Both swap behavior behind an interface, but State objects usually decide their own next state, whereas a Strategy is selected from outside and rarely changes itself.
3. Which standard-library API is a Strategy in disguise?
sort.Slice takes the comparison strategy as a function, letting you sort the same data any way you like without changing the sort algorithm.
4. Strategy is the book's first pattern because it showcases three core principles. Which?
The SimUDuck story pulls the varying behavior (fly/quack) out into interfaces, holds them by composition instead of subclassing, and has clients program to the behavior interface — the three principles Strategy is built to teach.
5. Strategy vs Template Method — what's the mechanism difference?
Template Method keeps the algorithm's outline in one place and lets subtypes fill in steps (inheritance/embedding, the Hollywood Principle). Strategy hands the entire algorithm in as an interchangeable object. Composition (Strategy) buys runtime swapping; the skeleton (Template Method) buys a guaranteed shape.
Comments
Sign in with GitHub to join the discussion.