Correctly using a language feature often means using it for more than one reason. Extending superclass code can be well advised in case it also makes sense semantically. Now, I have seen a "mixin" class that just served to provide one relatively independent method, and I turned it into a free function. Much better. In other cases, an extended class works on the whole state of the object. In that case it usually makes sense.
> Code reuse is in fact a prime example of a misuse of inheritance
Does that depend on how inheritance works in a particular language? For example, in Python it’s widely believed that inheritance is simply “a tool for code reuse” [0].
It's the main reason why inheritance became so popular and so useful.
Specialization remains a very common design pattern that is incredibly useful and trivially and intuitively solved with inheritance. No other programming concept (HKT, ad hoc polymorphism, functional programming, etc...) comes close to its elegance.
Code-reuse via "Implementation Inheritance" is completely unnecessary for specialisation or polymorphism.
When someone says that "inheritance is bad for code reuse" they're not talking about interfaces, or using inheritance for polymorphism. They're strictly talking about sharing code using implementation inheritance, which is the thing that has been widely criticised for more than 30 years now.
One can argue that even the "Template Method Pattern" doesn't fall into "implementation inheritance", since the implementation lives in the subclass.
If you read the posts, discussion is way more nuanced than "inheritance bad vs inheritance good".
Here is more specifically what I meant with my comment about specialization above. There is a class with four methods, three of which are exactly what you need but the fourth one, you need to modify.
Solving this with inheritance is trivial (extend and override).
Solving this with any other paradigm is... much harder and requires a lot more boilerplate.
In functional programming you'd just create a new function that takes a value of the same type of those other 3 methods.
But anyway, creating a new method without changing any of the methods of the super class I think it's generally ok. The problems arise from modifying methods that the super class already implemented.
But code that uses the old function wouldn't magically start invoking your new function instead, something that inheritance and polymorphism give you for free.
Nothing will magically start calling your new function. If you are defining a new function that didn't exist before, then you'll have to actively call it somewhere. What you're describing instead is overriding an inherited function. However, that is full of pitfalls, I would not call that "for free" by any means. There are example of the problems in this very thread. Anyway, that's distinct from polymorphsim, which is present in functional programming.
You may be missing the point that's being made, though. No one is arguing against interfaces, but overriding concrete methods from a concrete class. Those need to be well thought out as extension points for you to have any chance of having stable software. Not quite for free.
Interfaces with default implementations are better than classes for a number of reasons, including the diamond problem, and that as systems grow large they almost never fit cleanly into an inheritance tree. Fat pointers with two words, one for an interface vtable and one for the object, work really well.
In your view, how is "interfaces with default implementations" different from "inheritance for code reuse"? At a minimum it looks like a virtual function with a default implementation in the base class. If a derived class doesn't override it, isn't that code reuse?
> In your view, how is "interfaces with default implementations" different from "inheritance for code reuse"?
The latter is a more general classification?
A type which inherits a default method implementation from an interface is an example of "inheritance for code reuse".
A type which inherits a method implementation from a parent class under classical OOP is also an example of "inheritance for code reuse".
However, I argue that "interface inheritance with default implementations" is superior to "classical OOP" because it avoids tight coupling with memory layout, problems with implementing multiple inheritance in classical OOP, etc.
I believe that both Go and Rust use two-word fat pointers for interface types. I was actually thinking of Rust traits first and foremost; in Rust, traits can supply default method implementations which invoke other methods defined in the same trait.
While I take your point about introducing assumptions, I'm reluctant to give up the convenience of default implementations; they seem to present fewer problems than classically inherited methods because shallow hierarchies are more common with interfaces than with classical inheritance, and because default interface methods cannot directly access member variables because they do not know about object layout — unlike methods inherited from a parent class under classical inheritance.
> Interfaces with default implementations are better than classes for a number of reasons
it's literally the same thing in practice
> including the diamond problem,
the diamond problem has only ever been a "problem" in OOP textbooks, in years and years of working on OO system I have never saw it be a problem in practice
The plan seems so complex that it makes complete sense to me why languages would avoid multiple class inheritance (where each class is afforded direct access to member variables).
In contrast:
• With single inheritance, you don't have to resolve different member variable layouts because there is only one.
• With interface inheritance (with or without default methods), interface methods don't get to access struct members directly and know nothing about object memory layout.
> Interfaces with default implementations are better than classes for a number of reasons, including the diamond problem [...]
"The diamond problem" only occurs with multiple inheritance. At a (wild-ass) guess, only a minority of "traditional" (=inheritance-based) OOP languages have that; certainly not all of them. So as far as "the diamond problem" is concerned, interfaces with default implementations are no better than single-inheritance classes.
Inheritance is just a tool to mostly avoid. It tightly couples different class implementations. So will make later refactors error-prone and tend to scatter logic. It's misunderstanding OO as messaging between objects.
Using composition or lay out data differently may yield designs with more desirable properties. There's no one answer and it depends.
It’s a misuse because (if you really are only using it for code reuse) it’s creating an essential relationship between different objects that is actually incidental.
A list containing two different subclasses of your avoiding-copy-paste base class is now most likely a logical error.
You could just as well argue that there should never be any code reuse ever, code reuse always couples objects, code reuse should never be done if the two different parts doesn't have the same purpose. So the same rules for all code reuse also applies for inheritance, if you follow them then there are no problems using inheritance. If you have a set of data kinds which all needs to implement the same fields that has the same purposes then having a list of that parent class doesn't lead to bugs.
I don’t think it’s true that reuse means tight coupling. With composition you can swap out the composed object at runtime and as far as compile time coupling is concerned you can embed an interface to decouple the outer object from the inner object.
All code reuse creates coupling on some level. Creating more abstractions in between reduces coupling but creates code bloat. There is a trade off.
For example, if you call the same function from many locations those locations are now coupled since if you change the function you change all of those locations behaviour. Many times that is desirable, in which case it is good coupling. The exact same rule applies to inheritance.
Can you elaborate a bit? How is there coupling between a thing which uses an interface and a thing which implements the interface of the two don’t know about each other? Especially if the interface is implicit à la Go interfaces or structural subtyping?
There is no state to track. If there is, 99% composition has worked out way better for me in the long run since I don't have this really strong coupling between two objects.
So you don't have a need for code reuse then since you don't have many types with the same base structure, just say that instead of saying that it is bad.
There are often more straightforward ways to reuse code than to use inheritance. ~Two~ three advantages of composition for code reuse:
1. Composition is often more explicit than inheritance. Inheritance is overly magic.
2. Composition avoids incidental coupling of methods. With inheritance this is unavoidable.
3. Composition is more difficult to misuse. Both composition and inheritance have their purposes. In the wild, I’ve seen inheritance misapplied much more often than I have seen with composition.