Wednesday, January 24, 2007

Inheritance of Implementation is Evil

If you go to your first software engineering job interview fresh out of college, they might ask you: what are the three tenets of object-oriented programming? They're hoping you'll barf out "encapsulation, polymorphism, and inheritance" or something like that.

Of those, inheritance is probably the least important. They'll then ask why inheritance is a good thing, quite possibly hoping to hear those two dreaded words: "code reuse".

No!!!!!!!! Run for your lives! The sixty foot tall abominable derived classes are coming!

Inheritance of implementation happens any time you derive from a class that does things, and then try to change what that class does slightly by overriding part of its implementation. I would describe this as the code equivalent of an organ transplant - let's just rip out the pancreas, put a new one in (maybe it's not even the same species) and hope it all plays nice together.

I have come to the conclusion that inheritance of implementation is almost always a bad thing. I'm not saying never use it - there's no rule that always holds in software engineering. (Ha, sort that one out.) But in the case of inheritance of implementation, I think that, like virtual base classes, inheritance of implementation should be a red flag and cause for pause.

The problem with inheritance of implementation is that it makes it really easy to violate encapsulation, which is the most important thing in OOP. Parent-child classes make a difficult context to manage customization.

If you are going to do it, consider a known design pattern like "template method". Template method at least formalizes the relationship between parent and child class that get out of hand.

The temptation for inheritance of implementation is strong - when you've got an is-a relationship and the behavior seems to be driven by that relationship, how can you not want to "inherit" the behavior. But is-a describes a publicly described interface - the implementation code may have seams that aren't related to the is-a relationship. Pulling out pieces of implementation along these lines is asking for trouble.

The cure for inheritance of implementation is a "bridge" pattern - and I would go as far as to say there's no need to worry about whether the implementation is in a class hierarchy (use it only if it's useful) - the important thing is to make sure the implementation is designed based on what makes sense and not based on how the interface presents itself.

(I am not surprised to see Java in the Wikipedia article on "bridge" above - Java doesn't provide for multiple inheritance of implementation. I'm not a huge Java fan but in this case I think they got it 100% right in requiring coders to write a few more lines of code to avoid spaghetti.)

In my professional experience, inheritance of implementation happens a lot out of impatience - I have class A that almost does what I want, so I derive from it and make class B. The worst form of this involves declaring pieces of A that were not virtual to be virtual so that they can become overridden - changing the "API" of A after the fact.

The right thing to do would have been to refactor the code up-front; pull out from B the utility U that A wants to reuse, then A and B can call U. Eventually some one is going to do that refactoring, but it would have been a lot easier to see and do first when the implementation was all in A than when the implementation has been spit between A and B by random overriding of virtual methods.

2 comments:

  1. As I was reading the article I had problems agreeing with what you had to say. It isn't until I read the example that I understood and agreed. I do think it is a bad idea to use inheritance to reuse code in the way you describe. I have always preferred composition over inheritance since as you mentioned you end up changing original classes to achieve a certain effect in the child classes. This in turn will complicate your whole design and makes highly coupled classes.

    As a side note, I have recently started following your posts and have been enjoying the read. I found this post from your "What OOP Isn't" post.

    ReplyDelete
  2. Hi Danick,

    For what it's worth, part of my (over) reaction to inheritance-as-code-reuse comes from how it was originally presented to me...the original argument was:
    - To use a component, inherit from it.
    - This will let you reuse code that you couldn't otherwise reuse.
    - The reuse will make you more productive.
    And as you say, composition is usually the right answer...inheritance is (to the C++-esque languages) sort of like "composition with surprising side effects". And often all you need to get good reuse out of code is solid encapsulation and a good idea of where the boundaries are.

    (This was all before extreme programming and the notion that extending interfaces might be a normal part of development and not a sign that the original design was inadequate...)

    ReplyDelete