Strategy Pattern

Strategy Pattern Diagram

The strategy pattern (a.k.a policy pattern) allows selecting an algorithm at runtime. This is achieved by creating a family of objects that implement the same interface so client code can select which one of those objects (which strategy/algorithm) to use at runtime.

The user of the strategy is called the context and the algorithms are the strategies.

The strategy pattern is based on composition and delegation (not on class inheritance).

Use Cases

Report Generator

Report Generator Strategy Pattern Diagram

The context here is Report, and the strategies are PlainTextFormatter and HTMLFormatter. Report doesn’t know about the details of each report format. It just knows it can call output_format from the strategy @formatter and be done with it.

Ruby

formatter.rb:

##
# A base abstract class.
#
class Formatter
  ##
  # This method should be implemented by subclasses.
  #
  def output_report(_title, _text)
    raise 'Implement `output_report` in `Formatter` subclasses.'
  end
end

plain_text_formatter.rb:

require_relative 'formatter'

##
# Formats the report in Plain Text.
#
class PlainTextFormatter < Formatter
  def output_report(title, text)
    puts("===== #{title} =====")

    text.each do |line|
      puts(line)
    end
  end
end

html_formatter.rb:

require_relative 'formatter'

##
# Formats the report in HTML.
#
class HTMLFormatter < Formatter
  def output_report(title, text)
    puts('<html>')
    puts(' <head>')
    puts("<title>#{title}</title>")
    puts(' </head>')
    puts(' <body>')

    text.each do |line|
      puts("<p>#{line}</p>")
    end

    puts(' </body>')
    puts('</html>')
  end
end

report.rb:

##
# A reporter class that delegates reporting to instances of subclasses
# of `Formatter`.
#
class Report
  attr_reader :title, :text
  attr_accessor :formatter

  ##
  # We need an instance of `Formatter` here.
  #
  def initialize(formatter)
    @formatter = formatter

    @title = 'Nostromo Final Report'
    @text = [
      'This is Ripley, last survivor of the Nostromo.',
      'Signing off.'
    ]
  end

  ##
  # `output_report` is implemented in subclass of `Formatter`.
  #
  def output_report
    @formatter.output_report(@title, @text)
  end
end

Calculating Taxes

A tax calculator may choose different algorithms depending on the state or other attribute.

References