Skip to content

Instantly share code, notes, and snippets.

@zsan
Last active April 24, 2020 19:21
Show Gist options
  • Save zsan/c1216143c9fb1c8df3013b87479121a7 to your computer and use it in GitHub Desktop.
Save zsan/c1216143c9fb1c8df3013b87479121a7 to your computer and use it in GitHub Desktop.
ruby design pattern - Template Method - Strategy

So our first job is to create a class to make a report

class Report
  def initialize
    @title = 'Monthly Report'
    @text = [ 'Things are going', 'really, really well.' ] 
  end
  def output_report
    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

And we're going to use it with:

report = Report.new
report.output_report

Done.

Now you get new requirement, we need to produce text report along with the HTML and maybe RTF output soon.

easy, just use if else, so

def output_report
  if format == :plain
    # output plain
  elsif format == :html
    #output html
  elsif ...
  elsif ...
  end
end 

Done, but a mess.

Separate the Things That Stay the Same

The way out of this quandary is to refactor this mess into a design that separates the code for the various formats. The key in doing so is to realize that no matter which format is involved—whether plain text or HTML or the future PostScript—the basic flow of Report remains the same:

  1. Output any header information required by the specific format.
  2. Output the title.
  3. Output each line of the actual report.
  4. Output any trailing stuff required by the format.

So Define an abstract base class with a master method that performs the basic steps listed above, but that leaves the details of each step to a subclass.

With this approach, we have one subclass for each output format. Here is our new, abstract Report class:

So here's a better version now

# abstract class
class Report
  def initialize
    @title = 'Monthly Report'
    @text = ['Things are going', 'really, really well.']
  end
  
  # skeletal/template method
  # making calls to abstract methods, which are then supplied by the concrete subclasses.
  def output_report
    output_start
    output_head
    output_body_start
    output_body
    output_body_end
    output_end
  end

  def output_body
    @text.each do |line|
      output_line(line)
    end
  end
end

# concrete subclass
class HTMLReport < Report
  def output_start
    puts('<html>')
  end

  def output_head
    puts(' <head>')
    puts(" <title>#{@title}</title>")
    puts(' </head>')
  end

  def output_body_start
    puts('<body>')
  end

  def output_line(line)
    puts(" <p>#{line}</p>")
  end

  def output_body_end
    puts('</body>')
  end

  def output_end
    puts('</html>')
  end
end

# concrete subclass
class PlainTextReport < Report
  def output_start
  end

  def output_head
    puts("**** #{@title} ****")
    puts
  end

  def output_body_start
  end

  def output_line(line)
    puts(line)
  end

  def output_body_end
  end

  def output_end
  end
end


report = HTMLReport.new
report.output_report
report = PlainTextReport.new
report.output_report

the general idea of the Template Method pattern is to build an abstract base class with a skeletal method. This skeletal method (also called a template method) drives the bit of the processing that needs to vary, but it does so by making calls to abstract methods, which are then supplied by the concrete subclasses. We pick the variation that we want by selecting one of those concrete subclasses.

Strategy

What if, instead of creating a subclass for each variation, we tear out the whole annoyingly varying chunk of code and isolate it in its own class? Then we could create a whole family of classes, one for each variation. Here for example, is our HTML formatting code from the report example, surgically transplanted into its own class:

# context class
class Report
  attr_reader :title, :text
  attr_accessor :formatter

  def initialize(formatter)
    @title = 'Monthly Report'
    @text = [ 'Things are going', 'really, really well.' ]
    @formatter = formatter
  end

  # strategy - strategy method
  def output_report
    @formatter.output_report(self)
  end
end

# strategy class
class HTMLFormatter
  def output_report( context )
    puts('<html>')
    puts(' <head>')
    puts(" <title>#{context.title}</title>")
    puts(' </head>')
    puts(' <body>')
    context.text.each do |line|
      puts("    <p>#{line}</p>" )
    end
    puts('  </body>')
    puts('</html>')
  end
end

# strategy class
class PlainTextFormatter
  def output_report(context)
    puts("***** #{context.title} *****")

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

# report = Report.new(HTMLFormatter.new)
# report.output_report
# report.formatter = PlainTextFormatter.new
# report.output_report

The Strategy pattern is a delegation-based approach to solving the same problem as the Template Method pattern.

Instead of teasing out the variable parts of your algorithm and pushing them down into subclasses, you simply implement each version of your algorithm as a separate object.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment