Originally published on phpdesignpatterns.dev — part of the PHP design patterns course. View this lesson on the original site.
Strategy, Factory, and State: who decides what
Strategy is often confused with two other patterns because the structure looks similar — an interface and several implementations. The difference is in what changes and who decides.
Strategy vs neighbours — what is actually varying?
flowchart TD
q{{"What varies: rule at call time, object to build, or lifecycle phase?"}}
q -->|"rule / algorithm"| s[Strategy: swap policy behind one port]
q -->|"which class to construct"| f[Factory Method: creation choice]
q -->|"allowed actions for this phase"| st[State: delegate to current state object]
s --- n[Often pair: factory picks which strategy]
Strategy vs Factory Method (chapter 3)
flowchart LR
subgraph L["Strategy"]
l1["how the operation behaves at call time"]
end
subgraph R["Factory Method"]
r1["which concrete class to construct"]
end
l1 -.->|different job| r1
- Strategy answers: given a request, how should this operation behave? The variation is in what runs.
- Factory Method answers: given a request, what object should I create? The variation is in what gets built.
Often they pair. The factory chooses which strategy to instantiate; the strategy then runs. Don't conflate them — choosing and doing are separate concerns.
<?php
final class DiscountStrategyFactory
{
public function for(string $customerType): CalculatesDiscount
{
return match ($customerType) {
'guest' => new NoDiscount(),
'standard' => new StandardDiscount(),
'loyalty' => new LoyaltyDiscount(),
'employee' => new EmployeeDiscount(),
default => new NoDiscount(),
};
}
}
The factory is the only place that knows the mapping. The strategy is the only place that knows the rule.
Strategy vs State (chapter 15)
flowchart LR
subgraph L["Strategy"]
l1["caller injects swappable policy per call"]
end
subgraph R["State"]
r1["object owns phase behaviour that changes over time"]
end
l1 -.->|different job| r1
- Strategy is chosen once per call. The caller picks one, runs it, and forgets it.
- State is owned by an object and changes over time. The same object's behaviour shifts as it transitions between states.
If the choice depends on what was just done — Order::cancel() is allowed when pending but not when shipped — that's State, not Strategy.
Strategy vs Template Method (chapter 14)
flowchart LR
subgraph L["Strategy"]
l1["composition: swap whole strategy object"]
end
subgraph R["Template Method"]
r1["inheritance: override hooks in fixed skeleton"]
end
l1 -.->|different job| r1
- Strategy uses composition: the variation is a separate object passed in.
- Template Method uses inheritance: the variation is method overrides in a subclass.
Strategy is generally the better default because composition is more flexible than inheritance. Template Method earns its place when the algorithm has a fixed skeleton with a few small overridable steps and no real reason for the steps to swap at runtime.
What this means in practice
Most teams reach for Strategy too rarely (leaving the if chain to grow), and reach for State and Visitor too often (forcing patterns where Strategy + a small enum would do). Knowing the distinction stops you from over-engineering when a humbler pattern fits.
From PHP design patterns — Strategy: replacing branching with swappable behaviour