There are lots of relatively coherent arguments already floating around the interweb around why composition is generally preferable to inheritance when it comes to code reuse; lots of people have strong feelings both ways.
I should point out, however, that many OO languages (like Java and C++) make inheritance much easier than composition, and as such inheritance seems more useful than it actually is, simply because it's all you've got. In languages that have better syntactic support for delegation and other compositional techniques, the usefulness of inheritance is significantly lessened. In other words, the usefulness of inheritance is more accidental (i.e. because you often don't have other good options) rather than inherent (i.e. because there simply aren't better options that are possible or available in other languages).
off the top of my head: dynamic dispatch is a great way to re-use code, assuming the subclasses really have a "is-a" relationship.
this makes for a double whammy in terms of extensibility, since not only is the code literally more conducive to later changes, but it meets developer's expectations, too.
encapsulation and inheritance go hand-in-hand in my book, though i suppose encapsulation, and the necessary concept of interface contracts, is an axiom of sorts from which many other good practices can result, especially if there is also a culture of "communicating for extensibility by new developers later."
My experience is almost the exact opposite of yours! The first problem is that the expression "is-a" is notoriously vague. If class A has method a, and class B subclasses a, can B override method a? In what sense is B an A if it does not obey Strict Liskov Substitutability?
The second problem is that re-use is an easy problem to solve in any language. We have delegation, we have proxies, we have so many tools. Why use inheritance, which mixes two different concepts (is-a and was-a a/k/a is-implemented-using)?
Lastly, how is the code more conducive to later changes? In my experience, you now have a problem where changing class A changes the behaviour of every class that extends A. This is a notoriously tricky problem in the real world. In theory, that change ought to change every subclass and inheritance is a win. In practice, the people writing all those subclasses did so making assumptions about the behaviour of A at the time it was written, much as there are billions of lines of windows code that depend on its bugs and break the moment you fix a bug.
Encapsulation is a wonderful thing. I do not see implementation inheritance as having anything whatsoever to do with encapsulation. If anything, I see the opposite.
If I write that B is-a A, if these things are fully encapsulated, shouldn't it be a black box as to how B manages to behave exactly like an A? Maybe there's some shared code, but then again, shouldn't it be possible for B to use some other completely different implementation? That's encapsulation to me. Forcing interface inheritance to be twinned with implementation inheritance breaks encapsulation in my opinion.
I completely agree with your point. The way inheritance is always taught is through the "is-a" rule of thumb and I think that's so detrimental to the students that they'd be better off if they didn't know inheritance existed. The phrase "X is-a Y" often appears to make sense because of the real world (e.g. "Cat is-an Animal"), but it makes people forget that it may not make sense in the world they are trying to model. It may be that the relationship between a Cat object and an Animal object is completely different in your system than in the real world. If it is, then you run into the problems with future changes you already mentioned.
I also second your point on code reuse. Code reuse/removal of duplication is the main reason behind all programming languages and paradigms. Inheritance is a tool provided by OOP languages. Therefore, to say that it enables code reuse is basically trivial.
Yes, absolutely. Thinking about design in terms of defining an ontology is a great insight. Every object has a name and is therefore a part of some sort of semantics. However, what it does (it's meaning) is up to the designer. Therefore, words are naturally overloaded - they represent what we they do in reality, and their analog in our system.
As an example, let's assume we're developing a game where cats battle all sorts of other animals for survival. Certainly, the designer has good reason to call the object representing the protagonists in the game Cat. The programmer also has good reasons to call the enemies of the cat Animals, since they will all do the same thing (e.g. attack the cat). However, in this case a Cat is definitely not an Animal. Cat is an overloaded word - it no longer means "feline". Similarly, Animal means something completely different - it's more akin to "enemy". If we could substitute the "natural" ontology of these words with the one our objects define, we would be able to tell that the statement "A Cat is an Animal" is senseless.
I think OOP actually tries to take advantage of the "natural" ontologies we have constructed through experience. This is why this is such a subtle design issue. I'm not quite ready to say it "gets it backwards", but it certainly doesn't mirror the way we naturally look at the world.
I hope I'm mirroring and expanding on what's been said here. I'd love to read your thoughts.
You ask good questions. The right solution depends on the problem; it's difficult for me to disagree with your conclusions when I don't know the problem domain or framework around which your examples exist. Maybe we work on fundamentally different kinds of problems, or right code with fundamentally different structures, and thus where inheritance frequently works amazingly well for me, it fits poorly for you.
Extensibility of the type without breaking existing code. When you add or remove a case from an algebraic data type, you pretty much need to modify all functions that operate on it since they contain patterns that mention the data-constructors that you either removed, or they don't cover all the cases anymore.
(if you meant pattern matching as in text/regular expressions, disregard what I just wrote)
There is no need for (implementation) inheritance just to use dynamic dispatch. Many languages provide mechanisms for dynamic dispatch without needing to use inheritance.
Also, you can certainly have encapsulation even in pure C with proper use of header files and use of typedefs, though C in no way requires you to encapsulate anything. Ever wondered what is the FILE in FILE* that many C/Posix functions use? I certainly have. But it's not necessary to expose it to callers of functions that use it. So much for encapsulation and inheritance going hand in hand.
I find that it can be valuable in a GUI toolkit. If I want to change a small part of a UI component, I can subclass it, change what I want, keep the rest, and classes that used the old component can use the new one too, because of subtype polymorphism.
Composition can work for a lot of these problems too. It depends on the scale of the modification, I guess.
It's a pretty popular idea in OO circles, in that if you are really interested in the topic you should already have heard the arguments. And blog posts have to stop somewhere.
You can start at http://c2.com/cgi/wiki?ArgumentsAgainstOop , and follow the links out of "Inheritance" for at least a decent start at the topic if you've never heard of this criticism.
Yes, certainly, but if you follow just the links I cited it's at least a good introduction to the topics. Following everything in that page is somewhat... less recommended.