jm said:
Thank you.
Why wouldn't I have an abstract class and override the abstract method,
since all the obects inherit from the same top level object (ducks)?
Here you've hit upon why interfaces are useful in a single-inheritance
language.
What if you need to give the same capability to a wide range of objects
that aren't related by inheritance? In a single-inheritance language I
have two choices.
1. Twist the inheritance hierarchy. I can stick the functionality
somewhere in the existing hierarchy, or insert a new class that
contains that functionality somewhere in the hierarchy. Inevitably this
leads to classes that shouldn't have the functionality getting it
(because they inherit from some class for some reason other than
getting this functionality), and some classes that should have the
functionality not getting it (because they're elsewhere in the
hierarchy for some reason).
Take the duck example that was given by another poster. Do you really
want a RubberDuck inheriting from Duck? What if Duck inherits from
FlyingBird which inherits from Bird which inherits from Mammal?
RubberDucks can't fly, and they're not mammals. That's what I mean by
"twisting the inheritance hierarchy": do I shoehorn things in where
they logically don't belong just so that they can get some
functionality, just so that I can treat them as equivalent in certain
cases?
2. The other choice is to insert the functionality piecemeal. Write a
Quack method in RubberDuck, even though it doesn't inherit from Duck
and therefore the compiler has no way to know that the Duck.Quack()
method and the RubberDuck.Quack() method are semantically equivalent:
they do the same thing to disparate objects. Then I have to write ugly
code like this:
public void SoundAWarning(object thing)
{
Duck duckThing = thing as Duck;
if (duckThing != null)
{
duckThing.Quack();
}
else
{
RubberDuck rubberDuckThing = thing as RubberDuck;
if (rubberDuckThing == null)
{
throw new ArgumentException(String.Format("Argument must be
either Duck or RubberDuck, not {0}.", thing.GetType()), "thing");
}
else
{
rubberDuckThing.Quack();
}
}
}
What interfaces allow me to do is give the same functionality to
otherwise unrelated classes. With an IQuack interface definition, I can
do this:
public interface IQuack
{
void Quack();
}
public class Duck : FlyingBird, IQuack
{
public void Quack() { ... }
}
public class RubberDuck : WaterToy, IQuack
{
public void Quack() { ... }
}
Notice that RubberDuck isn't a Duck: it's a WaterToy, because that's
where it logically belongs in my inheritance hierarchy. However, it
shares some functionality with Duck, so I can write methods like this:
public void SoundAWarning(IQuack quacker)
{
quacker.Quack();
}
and this (admittedly silly) method doesn't need to know whether it was
passed a Duck (a living creature) or a RubberDuck (a water toy). All it
needs to know is that the object passed it is capable of quacking in
some fashion or other. So, SoundAWarning accepts a group of objects
that cuts across the inheritance hierarchy in an arbitrary way.
Now, this may seem a nicety until you realize that all of this is
understood by and enforced by the compiler. Now that you've told the
compiler that SoundAWarning accepts only objects that implement IQuack,
the compiler will check that you only ever pass it a Duck or a
RubberDuck (or something that inherits from either of these) and
nothing else. If you recall my ugly original SoundAWarning method
above, it took an "object" as its argument, effectively telling the
compiler, "allow the caller to pass me anything at all, and I'll figure
out if it's valid at runtime." You've moved a validity check from
runtime to compile time, and you have tighter, cleaner, clearer code.
It's all good.