I think there are a few of factors that haven't been mentioned yet.
First of all, at least in "pure OOP" (e.g., Smalltalk) where everything is an object, you have to twist your mind into a rather unnatural configuration to think of a number (for only one example) as an intelligent object instead of just a value -- since in reality, 21
(for example) really is just a value. This becomes especially problematic when on one hand you're told that a big advantage of OOP is modeling reality more closely, but you start off by taking what looks an awful lot like an LSD-inspired view of even the most basic and obvious parts of reality.
Second, inheritance in OOP doesn't follow most people's mental models very closely either. For most people, classifying things most specifically does not have anywhere close to the absolute rules necessary to create a class hierarchy that works. In particular, creating a class D
that inherits from another class B
means that objects of class D
share absolutely, positively all the characteristics of class B
. class D
can add new and different characteristics of its own, but all the characteristics of class B
must remain intact.
By contrast, when people classify things mentally, they typically follow a much looser model. For one example, if a person makes some rules about what constitutes a class of objects, it's pretty typical that almost any one rule can be broken as long as enough other rules are followed. Even the few rules that can't really be broken can almost always be "stretched" a little bit anyway.
Just for example, consider "car" as a class. It's pretty easy to see that the vast majority of what most people think of as "cars" have four wheels. Most people, however, have seen (at least a picture of) a car with only three wheels. A few of us of the right age also remember a race car or two from the early '80s (or so) that had six wheels -- and so on. This leaves us with basically three choices:
- Don't assert anything about how many wheels a car has -- but this tends to lead to the implicit assumption that it'll always be 4, and code that's likely to break for another number.
- Assert that all cars have four wheels, and just classify those others as "not cars" even though we know they really are.
- Design the class to allow variation in the number of wheels, just in case, even though there's a good chance this capability will never be needed, used, or properly tested.
Teaching about OOP often focuses on building huge taxonomies -- e.g., bits and pieces of what would be a giant hierarchy of all known life on earth, or something on that order. This raises two problems: first and foremost, it tends to lead many people toward focusing on huge amounts of information that's utterly irrelevant to the question at hand. At one point I saw a rather lengthy discussion of how to model breeds of dogs, and whether (for example) "miniature poodle" should inherit from "full sized poodle", or vice versa, or whether there should be an abstract base "Poodle" class, with "full-size poodle" and "miniature poodle" both inheriting from it. What they all seemed to ignore was that the application was supposed to deal with keeping track of licenses for dogs, and for the purpose at hand it was entirely adequate to have a single field named "breed" (or something on that order) with no modeling of the relationship between breeds at all.
Second, and almost importantly, it leads to focusing on the characteristics of the items, instead of focusing on the characteristics that are important for the task at hand. It leads toward modeling things as they are, where (most of the time) what's really needed is building the simplest model that will fill our needs, and using abstraction to fit the necessary sub-classes to fit the abstraction we've built.
Finally, I'll say once again: we're slowly following the same path taken by databases over the years. Early databases followed the hierarchical model. Other than focusing exclusively on data, this is single inheritance. For a short time, a few databases followed the network model -- essentially identical to multiple inheritance (and viewed from this angle, multiple interfaces aren't enough different from multiple base classes to notice or care about).
Long ago, however, databases largely converged on the relational model (and even though they aren't SQL, at this level of abstraction the current "NoSQL" databases are relational too). The advantages of the relational model are sufficiently well known that I won't bother repeating them here. I'll just note that the closest analog of the relational model we have in programing is generic programming (and sorry, but despite the name, Java generics, for one example, don't really qualify, though they are a tiny step in the right direction).