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.
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:
- Output any header information required by the specific format.
- Output the title.
- Output each line of the actual report.
- 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.
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.