Strategy
Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
Also known as: Policy
Participants
| Role | In this example | Responsibility |
|---|---|---|
| Strategy | #call(enemies) | The interface every targeting rule shares. |
| Concrete Strategy | First, Strongest, a lambda | One way of choosing a target each. |
| Context | Tower | Holds a strategy and delegates the choice of target to it. |
Ruby implementation
Enemy = Data.define(:name, :progress, :hp)
# The Context: a tower delegates "which enemy should I shoot?" to a strategy.
class Tower
def initialize(targeting:)
@targeting = targeting
end
def target(enemies) = @targeting.call(enemies)
def with_targeting(targeting) = Tower.new(targeting:)
end
# Concrete Strategies. The interface is #call(enemies), so a lambda also qualifies.
class First
def call(enemies) = enemies.max_by(&:progress)
end
class Strongest
def call(enemies) = enemies.max_by(&:hp)
endIn use
enemies = [Enemy.new("Goblin", 12.0, 30), Enemy.new("Troll", 4.5, 320)]
Tower.new(targeting: First.new).target(enemies).name # => "Goblin"
Tower.new(targeting: Strongest.new).target(enemies).name # => "Troll"
weakest = ->(all) { all.min_by(&:hp) }
Tower.new(targeting: weakest).target(enemies).name # => "Goblin"
“Furthest along the path” is the only rule the game’s towers know. As strategies, the player could choose one per tower.
Notes
Ruby idioms
- Name the interface
#call. Lambdas, procs andMethodobjects respond tocall, so a one-off strategy needs no class at all. Promote it to a class when it has settings or deserves a name. - Pass the strategy in. A keyword argument (
targeting:) makes the dependency obvious and easy to replace in tests. - Return a new context instead of changing it.
#with_targetinggives back a new tower, so comparing strategies side by side is safe.
Reach for it when
- A conditional picks between algorithms:
if kind == :cannon ... elsif kind == :frost .... - Different players, levels or settings need different rules for the same job.
Watch for
- Strategy vs State: a strategy is chosen by the client and usually stays put. A state changes itself as the object runs.
One interface, many algorithms. The client chooses which.