1

To provide some context, I've seen some comments lately that equate inheriting behavior from a supertype, with inheriting a pure interface with no behavior. But there are pretty significant, and different, consequences to each.

e.g. Here's a sample base class, and a "pure" interface (pseudocode):

class FlyingBird {
    void fly() {
        change state due to flight
    }
}

interface Flyable {
    void fly();
}

Now Let's say I have Swan and Dove. I'll subclass Swan from FlyingBird, and have Dove implement Flyable.

Swan extends FlyingBird {
    (no defnition for fly, because we inherit it from FlyingBird)
}

Dove implements Flyable {
    void fly() {
        ... must provide implementation for fly, because the interface only provides the signature
    }

Now, I can call both swan.fly() and dove.fly(), but swan gets its implementation from FlyingBird. The idiom "Favor composition over inheritance" tells me to favor the Dove implementation over Swan, with some sort of injected implementation, but "subtype polymorphism" doesn't seem to make that distinction.

The distinction is important, because, for example:

  • There's a growing movement in OO to make classes "final" or "non-extendable" by default, because of the complexities of inheriting behavior without breaking the super-classes' semtantics. In other words, some people are suggesting that one form of subtype prototyping should be disabled by default. (I haven't heard anyone suggest getting rid of interfaces in Java.)

  • "Favor composition over inheritance" differentiates sharing behavior through class inheritance vs. providing a common interface backed by a shared implementation.

In other words, there are practical and important differences between inheriting behavior and just inheriting an interface.

Does subtype polymorphism distinguish between inheriting behavior, or inheriting an interface?

  • 1
    Can you link to the aforementioned comments? – Robert Harvey Apr 9 '14 at 17:43
  • 1
    An interface is a contract stating an object will provide certain behavior: I am not sure what polymorphism has to do with this, unless a part of the question is missing. – user22815 Apr 9 '14 at 17:44
  • It's a twist on polymorphism that it relatively new to me. I've always just seen inheritance as "call the subclass" vs. polymorphism as "call the base class". But I've seen people breaking down polymorphism in different ways. See en.wikipedia.org/wiki/Subtyping – Rob Apr 9 '14 at 17:54
  • @Robert Harvey unfortunately the comments were in a couple different places & weren't very informative out of context. Basically they dismissed my question by saying it boiled down to subtype polymorphism due to inheriting an interface, but that didn't make sense to me. – Rob Apr 9 '14 at 17:59
  • Again, I am not sure what is being "distinguished" here. Polymorphism means that the correct behavior will be invoked for the interface used. – user22815 Apr 9 '14 at 17:59
2

Now, I can call both swan.fly() and dove.fly(), but swan gets its implementation from FlyingBird. The idiom "Favor composition over inheritance" tells me to favor the Dove implementation over Swan, but "subtype polymorphism" doesn't seem to make that distinction.

Does subtype polymorphism distinguish between inheriting behavior, or inheriting an interface?

It doesn't. The criteria for polymorphism is substitutability of behavior. It's possible to have subtype polymorphism without classes, implementation inheritance or interfaces being involved.

As an example, consider tuples - an immutable sequence of n values of possibly different types. E.g. the type (float, float) is the type of 2-tuples whose first element is an integer and whose second element is a floating point number. The only operations tuple types have is selection - retrieving the ith element of the tuple (for i <= n). So the only things we can do with an (float, float) value is retrieve the first element or the second element (the float).

Now consider the type (float, float, string). This is a distinct type from (float, float), but we can apply the same operations to it - we can retrieve the first element and second elements, and the types match. So we could substitute an (float, float, string) anywhere a (float, float) is expected and the program would still work.

A language's type system may or may not decide to allow such substitutions. On the one hand, allowing them increases the number of valid programs in the language. On the other hand, you're weakening the conditions you can assert through the type system - now you can't enforce that if a function takes an (float, float) as an argument, it's actually receiving an (float, float) and not some 3-tuple (or 4-tuple or...) that happens to be substitutable. You could, for example, inadvertently pass a (float, float, float) representing Red/Blue/Green pixel data to a function expecting a (float, float) representing a 2D point and the program would compile.

Note, however, that the language can't force user-defined types to be substitutable. Nothing stops you from creating a Penguin implements Flyable that simply throws an exception when you call fly(). The burden of ensuring substitutability falls upon the programmer. And if you deliberately design classes that break their superclass's contracts, you will inflict much pain and misery upon yourself and others. But this is nothing new - the type system can only ensure that if an expression returns a value, it's of the correct type. Just because I write a function double squareRoot(double x) it doesn't automatically mean I'll actually return the square root of x. I should, but the compiler can't force me.

As for why you should favor composition over inheritance, that's a different question.

  • I was actually hesitant to provide an example because I knew that the semantics of the example would instantly become more important than the question ;) but "composition over inheritance" is a very real and practical concern that "subtype polymorphism" seems to lose. – Rob Apr 9 '14 at 18:31
  • @RobY Was the answer what you were hoping for? – Doval Apr 9 '14 at 18:42
  • Sorry, yes! I should have added the "good answer, thank you!" but I ran to lunch instead. BTW I read your blog article on single-typed languages and really liked it. I've used that idea in conversation. – Rob Apr 9 '14 at 19:36
  • @RobY Glad to hear it. If you're referring to this blog post, I can't take credit for it. The blog belongs to Robert Harper, a professor/programming language researcher at Carnegie Mellon University and one of the creators of Standard ML. – Doval Apr 9 '14 at 19:51
  • I think the problem is that some people come at the idea of "inheriting an interface" from different angles, depending on their language of choice. For example, in Java "inheriting an interface" is a compile error, because you "implement an interface". And designing to interfaces (literally) is a central concern in Java. But in C++, you have to go out of your way to write a "pure virtual" class. The functional world has a different take on it as well. So I think if the idea of "subtype polymorphism" or "inheriting an interface" comes up, it suggests a disconnect in the basic views. – Rob Apr 9 '14 at 19:52
0

In your example, Flyable and FlyingBird are not related. Therefore there's no real distinction to be made for the compiler, it just sees two completely different objects that happens to each have a fly() method.

If you declare a Flyable variable and call its fly method, no matter what the instance is, it shall be the instance method matching the interface's method that will be called.

If, however, you declare a FlyingBird variable and affect a Swan instance to it, since FlyingBird knows nothing of Flyable then it's the base FlyingBird.fly() method that will be executed.

What might be confusing is if your FlyingBird was implementing Flyable but would still have its own fly method. Logically you would have your Flyable.fly() implementation on the FlyingBird.fly() method so again there wouldn't have a distinction to be made. However, some languages like allows hiding interface methods behind instance methods named completely differently, that could even be private. Therefore, if a bird variable was of type Flyable and the instance implementation would have renamed the method land(), it is that method that would be called. As little sense as it would make, it's perfectly legal for your class to determine that Flyable.fly() calls should be mapped to the land() method.

If however the variable was of FlyingBird type, then calling bird.fly() will call that exact method regardless of the Flyable interface.

Of course this is marginal because you'd normally never do something like that. Still, it shows how the compiler makes the distinction. An interface-typed variable will call the interface method, while a class-typed variable will call the method as it's defined in the class.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.