On Fri August 8 2003 2:36 pm, Chris Morris wrote:
> Ben Giddings wrote:
> I agree with Ryan's reply to you. If I only need the one method on the 
> object I'm interacting with -- should that one call not be allowed 
> simply because all of the other methods that I don't need aren't 
> consistent with what it *should* be?

Ouch, this is one of the most difficult-to-understand sentences I've seen in a 
long time.

I'll pretend I understand it and give an example:


#
# Get the contents of the object, if it acts like a string, 
# simply return it, if it acts like a file, return its contents, 
# if it acts like a frobnitz, foozle it.
#
def getContents(obj)
  if obj.respond_to? :read
    return obj.read
  elsif obj.respond_to? :to_str
    return obj.to_str
  elsif obj.respond_to? :foozle
    return obj.foozle
  end
end

class EmailMessageBody < String

  attr_accessor :read 

  def initialize
    @read = false  # By default, mark the message body as unread
  end
end

Imagine that the "getContents" method is buried deeply within the source code 
to a library somewhere.  The programmer would never see that method, even if 
he/she did see the commenting of the method.  Now what happens when I pass an 
EmailMessageBody into getContents?  It will see that it responds to "read" 
and return a boolean.  That's not what you want.  That's not what the 
documentation says, but that's what it will do.  This could be a really hard 
bug to track down.

Testing to see if the "read" method is supported is not really "duck typing", 
in the sense that you're not really checking to see if it's a duck.  Just 
because the object implements a certain method doesn't mean that that method 
does what you expect.

In certain cases, checking to see if a method exists is enough.  The method 
"to_str" is a prime example.  If someone implements that method in a way that 
doesn't return a string, they deserve whatever problems they get.  There are 
plenty of examples of how to_str is used everywhere else, and the function 
name is pretty descriptive.  Other functions are less obvious.

Somebody else gave this example (though I added the "implements Duck" that I 
think was just accidentally left out):

interface Duck
{
   void quack();
}

class MyDuck implements Duck
{
   void quack()
   {
      System.exit();
   }
}

Here the "implements Duck" is essentially an agreement saying "I want other 
peole to see this as a Duck".  You're free to break that agreement, but if 
you do, you can expect trouble.

On the other hand:

class Psychiatrist
{
  void quack()
  {
    quack_references++;
  }
  void shrink()
  {
    shrink_references++;
  }
}

Just because someone has a "quack" function/method, doesn't mean they want 
their object to be treated as a Duck.  When you create a class and add 
methods to it, should you always have to say: "Hmm, what's the most popular 
way _____ is used", and make sure yours acts the same way?

My understanding was always that "implements Foo" in Java meant: "my class 
will define the following methods, in the way that Foo describes, so you can 
treat my class as a Foo".

As for the subject of why it is useful to implement a set of methods rather 
than just the one you care about, consider a function like:

def dumpContents(obj)
  if obj.respond_to? :write
    obj.write(data)
    obj.flush if obj.respond_to? :flush
    obj.close if obj.respond_to? :close
  elsif ...
  end
end

I think it would be much more clear if you could simply say:

def dumpContents(obj)
  if obj.implements_interface? :WriteableIO
    obj.write(data)
    obj.flush
    obj.close
  elsif ...
  end
end

Ben