Philipp Meier <meier / meisterbohne.de> wrote in message news:<20020913101910.GC17997 / o-matic.net>...
> On Fri, Sep 13, 2002 at 02:22:25PM +0900, Patrick May wrote:
> > I'd be curious to see code that
> > 
> > a. calls for overloading on type
> > b. couldn't be handled by pushing code up to the classes of the
> > argument objects
> 
> Because of Rubys dynamic nature this is IMHO always possible. But is it
> good design? You will agree, that, as an example, code that formats a
> business object for reporting certainly does not belong into the business
> object class but in a reporting class.

Code should execute in the namespace close to its data.  That said,
Ruby lets you use modules to clearly separate the code within a given
namespace.  See below.

> A solution I proposed before is
> extracting this kind of methods into a seperate classes which go along
> the classes you propose the methods belong into. The polymorphic
> dispatching logic then dispatch by type (untested):
> 
> class PolyTest
> 	@@receivers = { :to html => { String => StringAdapter,
> 		                      Integer => IntegerToHTML },
> 	                :to rtf  => { String => StringAdapter,
> 		                      Integer => IntegerToRTF }
> 			}
> 
> 	def dispatch(method, index, *args)
> 		r = @@receivers[method][args[index].type]
> 		r.send(method, self, *args)
> 	end
> 
> 	def to html(doc, object)
> 		dispatch(:to html, 1, doc, object)
> 	end
> 
> 	def to rtf(doc, object)
> 		dispatch(:to rtf, 1, doc, object)
> 	end
> 
> 	def test
> 		to html("", "Test") # => calls StringAdapter.to html("", "Test")
> 		to rtf ("", "Test") # => calls StringAdapter.to rtf("", "Test")
> 		to html("", 1)      # => calls IntegerToHTML.to html("", 1)
> 		to rtf ("", 1)      # => calls IntegerToRTF.to rtf("", 1)
> 	end
> end
> 
> class StringAdapter
> 	def to html(doc, string)
> 		doc << "<b>String:</b> #{string}<br>"
> 	end
> 	def to rtf(doc, string)
> 		...
> 	end
> end
> 
> class IntegerToHTML
> 	def to html(doc, i)
> 		doc << "<i>Integer:</i> #{i}<br>"
> 	end
> end
> 
> class IntegerToRTF
> 	def to rtf(doc, i)
> 		...
> 	end
> end

or, using modules:

 class PolyTest
 	def test
 		"Test".to_html # =~ StringAdapter.to html("", "Test")
 		"Test".to_rfc  # =~ StringAdapter.to rtf("", "Test")
 		1.to_html      # =~ IntegerToHTML.to html("", 1)
 		1.to_rtf       # =~ IntegerToRTF.to rtf("", 1)
 	end
 end
 
module FormatingMethods
 	def to html
 		"<#{ self.tag }>#{ self.class.to_s }:" +
                "</#{self.tag}> #{self.to_s}<br>"
 	end
 	def to rtf
 		...
 	end
 end
 
 class String
        include FormatingMethods
        def tag
                "b"
        end
 end

 class Integer
        include FormatingMethods
        def tag
                "i"
        end
 end

Which is the preferable design?  Note the differences from the
perspective of a user of the api (PolyTest.test):

No mediating functions.  No arguments to forget when calling
:to_html/:to_rtf.  No binding between the output stream and
:to_html/:to_rtf, though I realize your example could be changed to
avoid that binding.

The implementation is also short (~10 fewer LOC) and simple -- not a
single conditional to implement this behavior!

Once you've decided to create an object, you shouldn't need anymore
conditionals on that type.  It's nice to have such conditionals, and I
use them in my code, but they are refactoring targets to me.

~ Patrick