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

RoleIn this exampleResponsibility
Strategy#call(enemies)The interface every targeting rule shares.
Concrete StrategyFirst, Strongest, a lambdaOne way of choosing a target each.
ContextTowerHolds a strategy and delegates the choice of target to it.

Ruby implementation

strategy.rb
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)
end

In 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 and Method objects respond to call, 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_targeting gives 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.