There's an analogy here between inheritance -> interfaces and pointers -> object references as in a language like Java. Object references are pointers, in effect, but syntactically pared down so that it's (much) harder to break them. They are less powerful but safer. Similarly, if you have inheritance—particularly the dreaded multiple inheritance—you have everything interfaces can do and then some, but you also have much more responsibility and a lot greater chance of creating bad designs. In both cases, it makes sense when teaching the concepts to teach the fuller, more powerful concept. In day to day work, however, many programmers will make fewer mistakes using the more limited concept.
To say this a bit more clearly, but perhaps more insultingly: inheritance, properly and fully understood, is useful for a massive range of situations, of which mixins and interfaces are a part. I find that relatively few programmers (and I've worked in development for more than twenty years and taught programming at the Master's level for 7: I've taught a lot of programmers) can really grok proper inheritance to this level; therefore when they try to use it they end up shooting themselves in the foot, then blaming the tool. For them it's better to use the more limited tool. But it's still good to know the broader tool.
Java-as-taught is often ancient Java, from before enhancements like default interface methods. The only non-annoying way to inherit behaviour in ancient Java is inheritance. Even modern Java lacks automatic implicit composition ala Go.
Java also encourages large interfaces with many methods. Without any ability to inherit behaviour for those methods, it's often less tiresome to inherit from a class instead. Go, by way of contrast, encourages tiny interfaces with, ideally, one or two methods.
In this example, we might suppose that a quintessential "vintage" Java-esque design wouldn't have a single-method "Priceable" interface. It'd have a CartItem interface with hundreds of methods, all of which you'd have to implement - even if only to stub out with exceptions - in order to implement it. So you wouldn't actually use an interface, or else you wouldn't be avoiding all that typing.
An interface isn't simply a class whose methods are all abstract. It is a distinct kind of type which has no precise analogue in C++ because the language constraints which enable it to exist don't apply in C++. Namely, C++ has multiple inheritance.
To abuse a car analogy, this situation is akin to asking why older drivers weren't taught to use cruise control, and the answer being that many older cars don't have cruise control. Whilst it's possible to "emulate" cruise control by simply maintaining constant speed manually, learning to do that isn't the same as learning how to use cruise control.
From [1]:
"Bjarne Stroustrup: I had a lot of problems explaining that to people and never quite understood why it was hard to understand. From the first days of C++, there were classes with data and classes without data. The emphasis in the old days was building up from a root with stuff in it, but there were always abstract base classes. In the mid to late eighties, they were commonly called ABCs (Abstract Base Classes): classes that consisted only of virtual functions. In 1987, I supported pure interfaces directly in C++ by saying a class is abstract if it has a pure virtual function, which is a function that must be overridden. Since then I have consistently pointed out that one of the major ways of writing classes in C++ is without any state, that is, just an interface.
From a C++ view there's no difference between an abstract class and an interface. Sometimes we use the phrase "pure abstract class," meaning a class that exclusively has pure virtual functions (and no data). It is the most common kind of abstract class. When I tried to explain this I found I couldn't effectively get the idea across until I introduced direct language support in the form of pure virtual functions. Since people could put data in the base classes, they sort of felt obliged to do so. People built the classic brittle base classes and got the classic brittle base class problems, and I couldn't understand why people were doing it. When I tried to teach the idea with abstract base classes directly supported in C++, I had more luck, but many people still didn't get it. I think it was a major failure in education on my part. I didn't imagine the problem well. That actually matches some of the early failures of the Simula community to get crucial new ideas across. Some new ideas are hard to get across, and part of the problem is a lot of people don't want to learn something genuinly new. They think they know the answer. And once we think we know the answer, it's very hard to learn something new. Abstract classes were described, with several examples, in The C++ Programming Language, Second Edition, in 1991, but unfortunately not used systematically throughout the book. "
Another use like Priceable and CartItem in modern Java is Connection and AutoCloseable (database connection objects). Anything AutoCloseble can be automatically closed after a try/catch block is done (with some specifics I glossed over on how). And a Connection is that, some specific form of DB connection. You also have a DB ResultSet that is AutoCloseable. So by having another interface extending another, or a class implementing the interfaces you can drive very related functionality on classes that may do very different things.
CartItem could extend/implement the one or two method Priceable, along with all its other methods.
Isn't a class with exclusively pure virtual methods effectively an interface?
Meyer's OOSC doesn't. It explains inheritance 1/3 through the book, after generics (parametric polymorphism) and Design by Contract. Incidentally, OOSC has this paragraph about inheritance that is worth remembering:
"Neither the Open-Closed principle nor redefinition in inheritance is a way to address design flaws, let alone bugs. If there is something wrong with a module, you should fix it — not leave the original as it is and try to correct the problem in a derived module. (The only potential exception to this rule is the case of flawed software which you are not at liberty to modify.) The Open-Closed principle and associated techniques are intended for the adaptation of healthy modules: modules that, although they may not suffice for some new uses, meet their own well-defined requirements, to the satisfaction of their own clients."
(Emphasis in the original.)
The Item class is not an example of a healthy module. It has been written in an ad-hoc fashion, without concerns about its requirements or thoughts about what contracts it should offer to its clients (i.e. how it interacts with other modules). Unsurprisingly, adapting it through inheritance results in breakage.
I think it's because people just starting out are able to grasp the concept of a duck is a bird is an animal AND you can reduce the code you write by saying all animals walk, birds fly, and ducks quack. So you are building on from the parent class.
With interfaces you are saying all implementations of animals walking can be different, so you're going to have to write code for each one to this contract. You're not nexessarily suggesting that you could reduce the code you write in that case. Then when you get to all other sorts of class composition it gets pretty hard to understand at first.
I think that's the main reason, it's easier to get started with. Also, CS teaching is nothing like real life development, as you stated, interfaces get used quite a bit more than the idea of concrete, or even abstract base classes but you just might have an abstract base class that implements part of the interface.
I want to chalk it up to inheritance being an inherently easier thing to think about in real world terms, with a name that has easily-understandable connotations, even if some of those connotations are probably harmful in the way they muddle up via leaky abstraction when thinking computationally. Whereas with mixins/interfaces...those terms have never made immediate sense to me.
And yet when it comes to implementing OOP, mixins/interfaces seem so profoundly more suitable as the go-to strategy that I wish I had learned exclusively about them, with inheritance being left for edge cases. Maybe I just had a bad curriculum or bad memory when it came to the lessons about interfaces. But if there is a stronger focus on inheritance when it comes to teaching OOP, is it due to the design of C++/Java? Or just the practicalities of teaching OOP? Teaching about classes seems like a good first step in teaching OOP, and teaching about inheritance requires fewer new concepts/syntax from defining a class. Whereas mixins/interfaces require introducing the concepts of modules.