Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Raganwald on Inheritance (raganwald.com)
22 points by 0x44 on April 7, 2008 | hide | past | favorite | 16 comments


Summary: If superclass has method X with behaviour Y, do not override X to have behaviour Z in a subclass, because this violates the spirit of inheritance semantics.

To be fair, his version is long, persuasive and well-written. And it has references.


And to be fair, your summary expresses the idea succinctly and directly. Bravo!

May I quote you?


Of course. I'm curious: Do you want to quote the summary, or the evaluation?


Granted there is a loss of clarity when a sub class changes a parent classes behavior. But, there are still times where it can be useful.

EX: You want to add a section to an online banking app that handles IRA accounts. It would be nice to reuse as much of the logging, balance transfer, and UI as possible but you don't want to alter well tested core classes. So you use inheritance to get something that works and is reasonably clean until you can refactor a cleaner solution.


Speaking as someone who has implemented bank accounts...

Would you agree that all of our problems go away if we exposed an Account interface rather than an Account class? Then the impact of our choice about how to re-use the implementation is localized.


That brings up new issues as you now of 2 Account classes with identical functions in some areas and slightly changed functions in others so keeping fixes up to date can be an issue.

IMO you can build an interface and an abstract class that implements that interface with 2 subclasses for checking Account vs IRA Account but I think the abstract classes works just fine on it's own.


In that particular organization there is an interface hierarchy that is distinct from its implementation hierarchy. And in the implementation hierarchy, they avoid overriding existing methods in favour of using abstract classes. This might be what you are suggesting.

The ultimate goal is to avoid using public inheritance to model WAS-A.


It is likely I'm misunderstanding, but why would you use inheritance instead of composition in your example?


This is why prototype based languages allow software to model our experiences much more accurately. Inheritance seems to rely on a platonic ideal, i.e. there is some universal sense of chair (parent class) from which office chair, rocking chair inherit.

In reality that's not how we think about chairs. We can imagine the first chair was a tree stump which was incrementally refined, eventually into office chair, rocking chair, etc.

When we wish to add heated chairs to our application we can simply add a heating element to our chair prototype. We can even have heated office chairs, heated rocking chairs which use the same heating element prototype. Much more flexible.


This approach fits the WAS-A relationship much better than the IS-A relationship. A heated chair WAS A chair, but we fitted it with a heater.

It might still have enough in common with the original chair that you can use it without any change on your part, but you never know, there was that accident when we decided an EjectorSeat WAS A Chair last year...


I bet the alternatives that were considered were:

    class Parent
    {
     void a() {...}
     void b() {...}
     void c() {...}
     void d() {...}
     ...
     void zzz() {...}
    }
    
    class B 
    {
     Parent m_a;
     void a() {m_a.a()}
     void b() {m_a.b()}
     void c() {m_a.c()}
     void d() { specialized logic }
     ...
     void zzz() {m_a.zzz()}

    }
vs.

    class B : Parent
    {
     void d() { specialized logic }
    }


I see. His example is meaningful only in a language that does not provide a convention for handling missing methods in the composed class. For instance, in ruby, I can distill your example to:

class Parent def a; ... end def b; ... end def c; ... end ... def zzzz; ... end end

class B def initialize @parent = Parent.new end

    def b; special logic end

    def method_missing(methodname)
        @parent.send(methodname)
    end
end


Your first example works fine until void a() Parent.a() calls Parent.d() and you need the code to call B.d().

An interface also works but you end up cutting and pasting a lot of the same code between the two classes.


Corollary: factor out interfaces.


Unfortunately, I've spent more-time-than-I-should time over the years debugging issues where a subclass was supposed to have overriden a superclass method's behavior but the subclass method had the wrong method signature (and thus was never called). Console log statements never lie..


The idea that different methods can have the same name but different signatures presents many challenges. IMO, if you are going to live with the drawbacks, you ought to be able to enjoy all of the benefits, such as you will find in full-features MOPs like in Common Lisp or pattern-matching languages like Haskell.

BTW, Ruby and Java both solve this problem. In Java, use the @override annotation. If you are not overriding a method, you get a compiler error.

Ruby "solves" the problem by only having one method per name. But you can mix in some magic and get pattern matching if you want.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: