Hacker News new | ask | show | jobs
by threatofrain 1874 days ago
Why is code reuse your prime example for bad inheritance? What should people do instead?
3 comments

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.

Maybe he meant something similar to Go interfaces, but they are abstract and coupled loosely with implementations after the fact.

That or abstract class/pure interface. There's no need for default implementations introducing assumptions in code.

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 a thing in Java and in some other languages, but most people don't know about them:

https://docs.oracle.com/javase/tutorial/java/IandI/defaultme...

I knew that Java added "default methods" to its interfaces a few years ago, but wasn't sure whether Java uses "fat pointers" like Rust and Go.
Because interfaces don’t confer a relationship between implementations, only that they have some common method signatures.
> Because interfaces don’t confer a relationship between implementations

Having the interface implement functions does exactly that, so your point only applies for interfaces without implementations.

> 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

Do you know if the mechanism in described by Stroustrup in 1989 for supporting multiple inheritance is actually used by modern C++ compilers?

https://www.usenix.org/legacy/publications/compsystems/1989/...

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.

In OO languages, inheritance gets the most bang for the buck when used for polymorphism.

All code reuse can be expressed with composition and delegation, bringing more flexibility, testability, cohesion, decoupling, etc, etc, etc.

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.