Template Method Pattern (GoF) without abstract methods in Ruby


(Michael Schwarze) #1

Hi!

I have some questions re. the Template Method Pattern (GoF) in Ruby.

I need to update a simple management reporting system and have to add HTML to the so far text-based reports. Looks to me like the classical use case for the Template Method Pattern. I learned from Russ Olsen’s book »Design Patterns in Ruby« that we do not have / use / „make up“ abstract methods in Ruby. But a lot of infos concerning this topic on the web use these 'abstract‘ methods by raising some kind of error upon calling the methods (see [1, 2, 3]).

Does anybody out there in real ruby code bases actually use this approach with made up 'abstract‘ methods?

I’ve come up with three simple and obviously contrived example implementations without using abstract methods (and without meta programming):
a) Inheritance from a simple(r) use case [4]
b) Inheritance from an 'abstract‘ class [5]
c) Composition by mixin of an 'abstract' module with the concerning template method(s) [6]

Anything I might have missed here? And which way is most idiomatic Ruby (if at all;-)

Many thanks!

Cheers,
Michael

···

~~~
[1]: https://medium.com/@dljerome/design-patterns-in-ruby-template-method-753443f5a1c8
[2]: https://github.com/pruett/ruby-patterns/blob/master/patterns/template_method.md
[3]: https://stackoverflow.com/questions/12873873/ruby-base-class-call-child-class-like-in-abstract-classes/12873925#12873925

[4]: Example 1 - Inheritance from a simple(r) use case:
# Represents a simple (text) report
class Report
  def initialize
    @title = 'My Report'
    @text = ['Line 1', 'Line 2.']
  end

  def output_report
    output = head
    output << body
    output << footer

    output
  end

  def head
    "#{@title}\n"
  end

  def body
    output = ''
    @text.each do |line|
      output << "#{line}\n"
    end

    output
  end

  def footer
    ''
  end
end

# Represents an HTML report, inherits from the simple text report
class HTMLReport < Report
  def head
    <<~HTML
      <html>
      <head>
      <title>#{@title}</title>
      </head>
    HTML
  end

  def body
    output = "<body>\n"
    @text.each do |line|
      output << "<p>#{line}</p>\n"
    end
    output << "</body>\n"

    output
  end

  def footer
    "</html>\n"
  end
end

[5]: Example 2 - Inheritance from an 'abstract‘ class:
# Represents an abstract report / template method(s)
class Report
  def initialize
    @title = 'My Report'
    @text = ['Line 1', 'Line 2.']
  end

  def output_report
    output = head
    output << body
    output << footer

    output
  end
end

# Represents a text report
class TextReport < Report
  def head
    "#{@title}\n"
  end

  def body
    output = ''
    @text.each do |line|
      output << "#{line}\n"
    end

    output
  end

  def footer
    ''
  end
end

# Represents an HTML report
class HTMLReport < Report
  def head
    <<~HTML
      <html>
      <head>
      <title>#{@title}</title>
      </head>
    HTML
  end

  def body
    output = "<body>\n"
    @text.each do |line|
      output << "<p>#{line}</p>\n"
    end
    output << "</body>\n"

    output
  end

  def footer
    "</html>\n"
  end
end

[6]: Example 3 - Mixin of an 'abstract' module with the template method(s):
# Template method(s) for a report
module Report
  def initialize
    @title = 'My Report'
    @text = ['Line 1', 'Line 2.']
  end

  def output_report
    output = head
    output << body
    output << footer

    output
  end
end

# Represents a text report
class TextReport
  include Report

  def head
    "#{@title}\n"
  end

  def body
    output = ''
    @text.each do |line|
      output << "#{line}\n"
    end

    output
  end

  def footer
    ''
  end
end

# Represents an HTML report
class HTMLReport
  include Report

  def head
    <<~HTML
      <html>
      <head>
      <title>#{@title}</title>
      </head>
    HTML
  end

  def body
    output = "<body>\n"
    @text.each do |line|
      output << "<p>#{line}</p>\n"
    end
    output << "</body>\n"

    output
  end

  def footer
    "</html>\n"
  end
end


(Austin Ziegler) #2

GoF is not a recipe book.

-a

···

On Thu, Jan 10, 2019 at 11:59 AM Michael Schwarze <michael@schwarze-web.de> wrote:

Hi!

I have some questions re. the Template Method Pattern (GoF) in Ruby.

I need to update a simple management reporting system and have to add HTML
to the so far text-based reports. Looks to me like the classical use case
for the Template Method Pattern. I learned from Russ Olsen’s book »Design
Patterns in Ruby« that we do not have / use / „make up“ abstract methods in
Ruby. But a lot of infos concerning this topic on the web use these
'abstract‘ methods by raising some kind of error upon calling the methods
(see [1, 2, 3]).

Does anybody out there in real ruby code bases actually use this approach
with made up 'abstract‘ methods?

I’ve come up with three simple and obviously contrived example
implementations without using abstract methods (and without meta
programming):
a) Inheritance from a simple(r) use case [4]
b) Inheritance from an 'abstract‘ class [5]
c) Composition by mixin of an 'abstract' module with the concerning
template method(s) [6]

Anything I might have missed here? And which way is most idiomatic Ruby
(if at all;-)

Many thanks!

Cheers,
Michael

~~~
[1]:
https://medium.com/@dljerome/design-patterns-in-ruby-template-method-753443f5a1c8
[2]:
https://github.com/pruett/ruby-patterns/blob/master/patterns/template_method.md
[3]:
https://stackoverflow.com/questions/12873873/ruby-base-class-call-child-class-like-in-abstract-classes/12873925#12873925

[4]: Example 1 - Inheritance from a simple(r) use case:
# Represents a simple (text) report
class Report
  def initialize
    @title = 'My Report'
    @text = ['Line 1', 'Line 2.']
  end

  def output_report
    output = head
    output << body
    output << footer

    output
  end

  def head
    "#{@title}\n"
  end

  def body
    output = ''
    @text.each do |line|
      output << "#{line}\n"
    end

    output
  end

  def footer
    ''
  end
end

# Represents an HTML report, inherits from the simple text report
class HTMLReport < Report
  def head
    <<~HTML
      <html>
      <head>
      <title>#{@title}</title>
      </head>
    HTML
  end

  def body
    output = "<body>\n"
    @text.each do |line|
      output << "<p>#{line}</p>\n"
    end
    output << "</body>\n"

    output
  end

  def footer
    "</html>\n"
  end
end

[5]: Example 2 - Inheritance from an 'abstract‘ class:
# Represents an abstract report / template method(s)
class Report
  def initialize
    @title = 'My Report'
    @text = ['Line 1', 'Line 2.']
  end

  def output_report
    output = head
    output << body
    output << footer

    output
  end
end

# Represents a text report
class TextReport < Report
  def head
    "#{@title}\n"
  end

  def body
    output = ''
    @text.each do |line|
      output << "#{line}\n"
    end

    output
  end

  def footer
    ''
  end
end

# Represents an HTML report
class HTMLReport < Report
  def head
    <<~HTML
      <html>
      <head>
      <title>#{@title}</title>
      </head>
    HTML
  end

  def body
    output = "<body>\n"
    @text.each do |line|
      output << "<p>#{line}</p>\n"
    end
    output << "</body>\n"

    output
  end

  def footer
    "</html>\n"
  end
end

[6]: Example 3 - Mixin of an 'abstract' module with the template method(s):
# Template method(s) for a report
module Report
  def initialize
    @title = 'My Report'
    @text = ['Line 1', 'Line 2.']
  end

  def output_report
    output = head
    output << body
    output << footer

    output
  end
end

# Represents a text report
class TextReport
  include Report

  def head
    "#{@title}\n"
  end

  def body
    output = ''
    @text.each do |line|
      output << "#{line}\n"
    end

    output
  end

  def footer
    ''
  end
end

# Represents an HTML report
class HTMLReport
  include Report

  def head
    <<~HTML
      <html>
      <head>
      <title>#{@title}</title>
      </head>
    HTML
  end

  def body
    output = "<body>\n"
    @text.each do |line|
      output << "<p>#{line}</p>\n"
    end
    output << "</body>\n"

    output
  end

  def footer
    "</html>\n"
  end
end

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

--
Austin Ziegler • halostatue@gmail.com • austin@halostatue.ca
http://www.halostatue.ca/http://twitter.com/halostatue


(Michael Schwarze) #3

Hi Austin,

Thanks; I’m from a different programming language background and curious whether there is kind of a 'pattern of implementation‘ for implementing the Template Method Pattern in Ruby. My 'research‘ so far brought contra-dictionary results: the book on design patterns in Ruby saying not to use 'abstract‘ methods, but showing them in the respective examples and the examples found on the web |1, 2, 3] showing these 'abstract‘ methods in Ruby, too.

Hence my first question, to understand which approach to implementing the Template Method Pattern Ruby developers have chosen. I think that’s one of the ideas of design patterns: to have and guide a discussion around common or recurring design challenges. How do you approach this in Ruby? Do you prefer to inherit from a simpler use case / class, an 'abstract‘ class with these made up 'abstract‘ methods (no content, just raising an error) or do you prefer mixin modules? Why so?

As I’ve got a real world problem in one of my Ruby projects to solve, which reminded me of the Template Method Pattern, I’ve made up three possible design examples [4, 5, 6] for solving this, which would all work. But which way is ’the Ruby way’ here? So it’s actually more about trying to better understand OO in Ruby than about pattern.

Does this make sense to anybody?

Many thanks!

Cheers,
Michael

···

Am 10.01.2019 um 20:23 schrieb Austin Ziegler <halostatue@gmail.com>:

GoF is not a recipe book.

-a

On Thu, Jan 10, 2019 at 11:59 AM Michael Schwarze <michael@schwarze-web.de> wrote:
Hi!

I have some questions re. the Template Method Pattern (GoF) in Ruby.

I need to update a simple management reporting system and have to add HTML to the so far text-based reports. Looks to me like the classical use case for the Template Method Pattern. I learned from Russ Olsen’s book »Design Patterns in Ruby« that we do not have / use / „make up“ abstract methods in Ruby. But a lot of infos concerning this topic on the web use these 'abstract‘ methods by raising some kind of error upon calling the methods (see [1, 2, 3]).

Does anybody out there in real ruby code bases actually use this approach with made up 'abstract‘ methods?

I’ve come up with three simple and obviously contrived example implementations without using abstract methods (and without meta programming):
a) Inheritance from a simple(r) use case [4]
b) Inheritance from an 'abstract‘ class [5]
c) Composition by mixin of an 'abstract' module with the concerning template method(s) [6]

Anything I might have missed here? And which way is most idiomatic Ruby (if at all;-)

Many thanks!

Cheers,
Michael

~~~
[1]: https://medium.com/@dljerome/design-patterns-in-ruby-template-method-753443f5a1c8
[2]: https://github.com/pruett/ruby-patterns/blob/master/patterns/template_method.md
[3]: https://stackoverflow.com/questions/12873873/ruby-base-class-call-child-class-like-in-abstract-classes/12873925#12873925

[4]: Example 1 - Inheritance from a simple(r) use case:
# Represents a simple (text) report
class Report
  def initialize
    @title = 'My Report'
    @text = ['Line 1', 'Line 2.']
  end

  def output_report
    output = head
    output << body
    output << footer

    output
  end

  def head
    "#{@title}\n"
  end

  def body
    output = ''
    @text.each do |line|
      output << "#{line}\n"
    end

    output
  end

  def footer
    ''
  end
end

# Represents an HTML report, inherits from the simple text report
class HTMLReport < Report
  def head
    <<~HTML
      <html>
      <head>
      <title>#{@title}</title>
      </head>
    HTML
  end

  def body
    output = "<body>\n"
    @text.each do |line|
      output << "<p>#{line}</p>\n"
    end
    output << "</body>\n"

    output
  end

  def footer
    "</html>\n"
  end
end

[5]: Example 2 - Inheritance from an 'abstract‘ class:
# Represents an abstract report / template method(s)
class Report
  def initialize
    @title = 'My Report'
    @text = ['Line 1', 'Line 2.']
  end

  def output_report
    output = head
    output << body
    output << footer

    output
  end
end

# Represents a text report
class TextReport < Report
  def head
    "#{@title}\n"
  end

  def body
    output = ''
    @text.each do |line|
      output << "#{line}\n"
    end

    output
  end

  def footer
    ''
  end
end

# Represents an HTML report
class HTMLReport < Report
  def head
    <<~HTML
      <html>
      <head>
      <title>#{@title}</title>
      </head>
    HTML
  end

  def body
    output = "<body>\n"
    @text.each do |line|
      output << "<p>#{line}</p>\n"
    end
    output << "</body>\n"

    output
  end

  def footer
    "</html>\n"
  end
end

[6]: Example 3 - Mixin of an 'abstract' module with the template method(s):
# Template method(s) for a report
module Report
  def initialize
    @title = 'My Report'
    @text = ['Line 1', 'Line 2.']
  end

  def output_report
    output = head
    output << body
    output << footer

    output
  end
end

# Represents a text report
class TextReport
  include Report

  def head
    "#{@title}\n"
  end

  def body
    output = ''
    @text.each do |line|
      output << "#{line}\n"
    end

    output
  end

  def footer
    ''
  end
end

# Represents an HTML report
class HTMLReport
  include Report

  def head
    <<~HTML
      <html>
      <head>
      <title>#{@title}</title>
      </head>
    HTML
  end

  def body
    output = "<body>\n"
    @text.each do |line|
      output << "<p>#{line}</p>\n"
    end
    output << "</body>\n"

    output
  end

  def footer
    "</html>\n"
  end
end

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

--
Austin Ziegler • halostatue@gmail.com • austin@halostatue.ca
http://www.halostatue.ca/http://twitter.com/halostatue

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>


(Andy Jones) #4

My 10p:

Many of these patterns were designed for older languages with more basic features. I looked up Template Method -- and, please correct me if I've got the wrong end of the stick -- this is just polymorphism, a basic feature of OOP?

I know Ruby coders often shy away from inheritance (with good reason) but personally this is probably one of those times to consider using it.

Obviously Ruby doesn't have abstract classes or abstract methods. But AFAIK the idiomatic way to simulate an abstract method in Ruby is:

···

#########
def foo
  fail NotImplementedError, "You need to override this method"
end
#########

...and of course an "abstract class" is any class that has an abstract method in it.

I'm sure that there are dozens of other ways to achieve this goal, other than using inheritance. And, as I say, perhaps I've misunderstood the problem. But for my money if it looks like polymorphism then you want inheritance.

Click here to view Company Information and Confidentiality Notice.<http://www.jameshall.co.uk/index.php/small-print/email-disclaimer>

Please note that we have updated our privacy policy in line with new data protection regulations. Please refer to our website to view the ways in which we handle your data.


(Greg Navis) #5

I personally do add "abstract" methods that raise errors. It makes things
more explicit than having being thrown NoMethodError in case I forget to
implement it and makes it simple to identify which methods I need to
implement/override in subclasses.

I haven't seen your code but assuming the data structures representing the
report are traversed the same for all formats you have two options:

1. Use the template method pattern as you said.
2. Make a ReportGenerator class that accepts ReportRenderer as an argument.

It's difficult to say more without seeing the code though.

Greg


(Dan Itsara) #6

Not sure whether or not this is applicable in your situation, but could you
instead write a method that accepts a block?

Example:

def foo(i, really, want, these, args = {})
  yield(i, really, want, these, args) if block_given?
end

(obviously you could do other things in your method as well to DRY up
duplicate logic)


(Austin Ziegler) #7

But that’s my point. If you’re trying to implement the `Template Method
Pattern`, you’re trying to use GoF as a recipe book. This is a mistake and
will only hinder your learning of Ruby. What problem are you actually
trying to solve? I have yet to see a place where I have needed to implement
most of the GoF patterns in Ruby—some of these are because the language
provides features that supplant the need for such a pattern. In Ruby, you
quite *literally* don’t need abstract methods.

class Processor
  def process
    step1
    step2
    step3
    finalize
  end

  private

  def finalize
    # finalize the process here
  end
end

class Foo < Processor
  private

  def step1; end
  def step2; end
  def step3; end
end

If I do Processor.new.process, I will get a NoMethodError because I have no
implementation of `step1`, `step2`, or `step3`. If I do `Foo.new.process`,
it works. You don’t need abstract methods in Ruby because the methods don’t
have to exist during compilation, because there’s no compilation phase or
static method resolution.

I don’t try to solve the template method pattern because I’ve been
developing long enough that I recognize that pattern descriptions are
forensic descriptions of common implementation patterns—often not necessary
in languages with advanced and/or functional features (they have
*different* patterns that can be forensically described) and not an
instruction manual or language comparison tool.

-a

···

On Fri, Jan 11, 2019 at 4:08 AM Michael Schwarze <michael@schwarze-web.de> wrote:

Hi Austin,

Thanks; I’m from a different programming language background and curious
whether there is kind of a 'pattern of implementation‘ for implementing the
Template Method Pattern in Ruby. My 'research‘ so far brought
contra-dictionary results: the book on design patterns in Ruby saying not
to use 'abstract‘ methods, but showing them in the respective examples and
the examples found on the web |1, 2, 3] showing these 'abstract‘ methods in
Ruby, too.

--
Austin Ziegler • halostatue@gmail.com • austin@halostatue.ca
http://www.halostatue.ca/http://twitter.com/halostatue


(Michael Schwarze) #8

Hi Andy,

···

Am 11.01.2019 um 10:28 schrieb Andy Jones <Andy.Jones@jameshall.co.uk>:

Obviously Ruby doesn't have abstract classes or abstract methods. But AFAIK the idiomatic way to simulate an abstract method in Ruby is:

#########
def foo
fail NotImplementedError, "You need to override this method"
end
#########

...and of course an "abstract class" is any class that has an abstract method in it.

That’s exactly what I meant with 'abstract‘ method and what got me confused: As you said and Russ Olson wrote, Ruby doesn’t have them. Should we then really simulate them? The examples I found on the web did it but do they exist in real Ruby code bases / do you use them or are they just examples?

Many thanks!

Cheers,
Michael


(Michael Schwarze) #9

Hi Greg,

I personally do add "abstract" methods that raise errors.

Great, so it’s not just examples in blog posts, but real.

It makes things more explicit than having being thrown NoMethodError in case I forget to implement it and makes it simple to identify which methods I need to implement/override in subclasses.

Making your design decisions explicit by using these 'abstract‘ methods sounds like a good idea to me.

2. Make a ReportGenerator class that accepts ReportRenderer as an argument.

Will think about this and try it...

Many thanks for your advice!

Cheers,
Michael

···

Am 11.01.2019 um 11:09 schrieb Greg Navis <contact@gregnavis.com>:


(Michael Schwarze) #10

Hi Dan,

···

Am 11.01.2019 um 12:23 schrieb Dan Itsara <dan@glazziq.com>:

Not sure whether or not this is applicable in your situation, but could you instead write a method that accepts a block?

That’s an additional way I haven’t thought about so far and I’ll try!

Thanks & Regards,
Michael


(Michael Schwarze) #11

Hi Austin,

Many thanks for your patience and your explanations!

But that’s my point. If you’re trying to implement the `Template Method Pattern`, you’re trying to use GoF as a recipe book. This is a mistake and will only hinder your learning of Ruby.

Sorry, my English is probably worse than my Ruby :wink: I’m not really focussed on implementing the pattern 'by the book‘ but I think my problem is in the realm of the Template Method Pattern...

What problem are you actually trying to solve?

A simple management report which was text-only so far and should get an additional HTML output now; both following the same process / outline in generating the slightly differing output. That’s why I supplied the three made-up code examples with my initial email.

In Ruby, you quite *literally* don’t need abstract methods.

That was my understanding from the book, too. I did a web research but the results confused me, as they contained mainly abstract methods in Ruby. Hence my request to this list.

class Processor
  def process
    step1
    step2
    step3
    finalize
  end

  private

  def finalize
    # finalize the process here
  end
end

class Foo < Processor
  private

  def step1; end
  def step2; end
  def step3; end
end

Code helps - that’s the discussion I was looking for! That’s close to my example 2...

If I do Processor.new.process, I will get a NoMethodError because I have no implementation of `step1`, `step2`, or `step3`. If I do `Foo.new.process`, it works. You don’t need abstract methods in Ruby because the methods don’t have to exist during compilation, because there’s no compilation phase or static method resolution.

Ok, I get this. So you are suggesting inheritance here, but why use a class you will probably never instantiate? Why not mixin a module with the common methods (my example 3)?

  module Processor
    def process
      step1
      step2
      step3
      finalize
    end
    
    def finalize; end
  end

  class Foo
    include Processor

    def step1; end
    def step2; end
    def step3; end
  end

  class Bar
    include Processor

    def step1; end
    def step2; end
    def step3; end
  end

Or, when preferring inheritance, why not inheriting from the simple use case (my example 1):

  class Foo # simple text
    def process
      step1
      step2
      step3
      finalize
    end

    def step1; end
    def step2; end
    def step3; end
    def finalize; end
  end

  class Bar # complex HTML
    def process
      step1
      step2
      step3
      finalize
    end

    def step1; end
    def step2; end
    def step3; end
    def finalize; end
  end

All three approaches do work but which one would do Ruby experts prefer for solving this problem?

Cheers,
Michael

···

Am 11.01.2019 um 17:52 schrieb Austin Ziegler <halostatue@gmail.com>:


(Austin Ziegler) #12

If you’re making a report generator, you would want to use either
inheritance or decoration.

class ReportGenerator
  def initialize(thing_to_report_on)
    @thing_to_report_on = thing_to_report_on
  end

  def generate
    parse
    format
    output
  end
end

You might implement `parse` and `output` the same in the `ReportGenerator`,
but `format` would be different per sub-class.

The ultimate decoration, however, would be to have your generator, and then
do:

generator = ReportGenerator.new(data_for_report)
generator.extend(HTMLReportFormatter)
generator.generate

That’s a per-object mixin.

You can also do this with separate pieces:

class ReportGenerator
  def prepare
  end
end

class HTMLReportFormatter; def format(prepared_report); ...; end; end
class TextReportFormatter; def format(prepared_report); ...; end; end

rg = ReportGenerator.new(item).prepare
HTMLReportFormatter.new(rg)

What makes the most sense is what reads best to you and what will be
easiest to maintain.

For me, that depends on the rest of the code and what I’m trying to solve.
I have pretty much solved all of these in different ways every time, but
with Ruby I tend to shift toward duck-typing as much as possible.

-a

···

On Fri, Jan 11, 2019 at 5:20 PM Michael Schwarze <michael@schwarze-web.de> wrote:

Hi Austin,

> Am 11.01.2019 um 17:52 schrieb Austin Ziegler <halostatue@gmail.com>:

Many thanks for your patience and your explanations!

> But that’s my point. If you’re trying to implement the `Template Method
Pattern`, you’re trying to use GoF as a recipe book. This is a mistake and
will only hinder your learning of Ruby.

Sorry, my English is probably worse than my Ruby :wink: I’m not really
focussed on implementing the pattern 'by the book‘ but I think my problem
is in the realm of the Template Method Pattern...

> What problem are you actually trying to solve?

A simple management report which was text-only so far and should get an
additional HTML output now; both following the same process / outline in
generating the slightly differing output. That’s why I supplied the three
made-up code examples with my initial email.

> In Ruby, you quite *literally* don’t need abstract methods.

That was my understanding from the book, too. I did a web research but the
results confused me, as they contained mainly abstract methods in Ruby.
Hence my request to this list.

> class Processor
> def process
> step1
> step2
> step3
> finalize
> end
>
> private
>
> def finalize
> # finalize the process here
> end
> end
>
> class Foo < Processor
> private
>
> def step1; end
> def step2; end
> def step3; end
> end

Code helps - that’s the discussion I was looking for! That’s close to my
example 2...

> If I do Processor.new.process, I will get a NoMethodError because I have
no implementation of `step1`, `step2`, or `step3`. If I do
`Foo.new.process`, it works. You don’t need abstract methods in Ruby
because the methods don’t have to exist during compilation, because there’s
no compilation phase or static method resolution.

Ok, I get this. So you are suggesting inheritance here, but why use a
class you will probably never instantiate? Why not mixin a module with the
common methods (my example 3)?

  module Processor
    def process
      step1
      step2
      step3
      finalize
    end

    def finalize; end
  end

  class Foo
    include Processor

    def step1; end
    def step2; end
    def step3; end
  end

  class Bar
    include Processor

    def step1; end
    def step2; end
    def step3; end
  end

Or, when preferring inheritance, why not inheriting from the simple use
case (my example 1):

  class Foo # simple text
    def process
      step1
      step2
      step3
      finalize
    end

    def step1; end
    def step2; end
    def step3; end
    def finalize; end
  end

  class Bar # complex HTML
    def process
      step1
      step2
      step3
      finalize
    end

    def step1; end
    def step2; end
    def step3; end
    def finalize; end
  end

All three approaches do work but which one would do Ruby experts prefer
for solving this problem?

Cheers,
Michael

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

--
Austin Ziegler • halostatue@gmail.com • austin@halostatue.ca
http://www.halostatue.ca/http://twitter.com/halostatue


(Michael Schwarze) #13

Hi Austin,

Many thanks again! Took me a while to get my head around this per-object mixin but I think I got it now. I have only used include / extend to 'import‘ source code into my classes so far and wasn’t aware of this mixin feature at runtime / per object. But then Ruby being a dynamic language this make of course perfect sense.

Will stick with this approach for my reporting problem…

Cheers,
Michael

···

Am 11.01.2019 um 23:53 schrieb Austin Ziegler <halostatue@gmail.com>:

If you’re making a report generator, you would want to use either inheritance or decoration.

class ReportGenerator
def initialize(thing_to_report_on)
   @thing_to_report_on = thing_to_report_on
end

def generate
   parse
   format
   output
end
end

You might implement `parse` and `output` the same in the `ReportGenerator`, but `format` would be different per sub-class.

The ultimate decoration, however, would be to have your generator, and then do:

generator = ReportGenerator.new(data_for_report)
generator.extend(HTMLReportFormatter)
generator.generate

That’s a per-object mixin.

You can also do this with separate pieces:

class ReportGenerator
def prepare
end
end

class HTMLReportFormatter; def format(prepared_report); ...; end; end
class TextReportFormatter; def format(prepared_report); ...; end; end

rg = ReportGenerator.new(item).prepare
HTMLReportFormatter.new(rg)

What makes the most sense is what reads best to you and what will be easiest to maintain.

For me, that depends on the rest of the code and what I’m trying to solve. I have pretty much solved all of these in different ways every time, but with Ruby I tend to shift toward duck-typing as much as possible.

-a

On Fri, Jan 11, 2019 at 5:20 PM Michael Schwarze <michael@schwarze-web.de> wrote:
Hi Austin,

Am 11.01.2019 um 17:52 schrieb Austin Ziegler <halostatue@gmail.com>:

Many thanks for your patience and your explanations!

But that’s my point. If you’re trying to implement the `Template Method Pattern`, you’re trying to use GoF as a recipe book. This is a mistake and will only hinder your learning of Ruby.

Sorry, my English is probably worse than my Ruby :wink: I’m not really focussed on implementing the pattern 'by the book‘ but I think my problem is in the realm of the Template Method Pattern...

What problem are you actually trying to solve?

A simple management report which was text-only so far and should get an additional HTML output now; both following the same process / outline in generating the slightly differing output. That’s why I supplied the three made-up code examples with my initial email.

In Ruby, you quite *literally* don’t need abstract methods.

That was my understanding from the book, too. I did a web research but the results confused me, as they contained mainly abstract methods in Ruby. Hence my request to this list.

class Processor
def process
   step1
   step2
   step3
   finalize
end

private

def finalize
   # finalize the process here
end
end

class Foo < Processor
private

def step1; end
def step2; end
def step3; end
end

Code helps - that’s the discussion I was looking for! That’s close to my example 2...

If I do Processor.new.process, I will get a NoMethodError because I have no implementation of `step1`, `step2`, or `step3`. If I do `Foo.new.process`, it works. You don’t need abstract methods in Ruby because the methods don’t have to exist during compilation, because there’s no compilation phase or static method resolution.

Ok, I get this. So you are suggesting inheritance here, but why use a class you will probably never instantiate? Why not mixin a module with the common methods (my example 3)?

module Processor
   def process
     step1
     step2
     step3
     finalize
   end

   def finalize; end
end

class Foo
   include Processor

   def step1; end
   def step2; end
   def step3; end
end

class Bar
   include Processor

   def step1; end
   def step2; end
   def step3; end
end

Or, when preferring inheritance, why not inheriting from the simple use case (my example 1):

class Foo # simple text
   def process
     step1
     step2
     step3
     finalize
   end

   def step1; end
   def step2; end
   def step3; end
   def finalize; end
end

class Bar # complex HTML
   def process
     step1
     step2
     step3
     finalize
   end

   def step1; end
   def step2; end
   def step3; end
   def finalize; end
end

All three approaches do work but which one would do Ruby experts prefer for solving this problem?

Cheers,
Michael

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>

--
Austin Ziegler • halostatue@gmail.com • austin@halostatue.ca
http://www.halostatue.ca/http://twitter.com/halostatue

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk>