Until 1.8 Java’s composition over inheritance story was weak. 1.8 introduced default functions on interfaces, which finally allows shared functionality without inheritance. In some senses Java’s patterns in this area have been reset, and many old code bases need to catch up (and probably never will).
Well, from where I sit, interfaces are another kind of inheritance - inheritance of API rather than implementation. But once you can have default functions, that to me looks exactly like inheritance of a base class, except that the base is called "interface" instead of "class".
It's a difference of inheriting data vs. functionality, so yes, the API is what's being inherited.
Looking at the history of inheritence a few languages I know:
- C++: multiple inheritance super confusing expressions and understanding of things like diamond inheritance. Initialization becoming a really complex thing to understand.
- Java: learning from the mistake of C++, determines multiple inheritance is bad, only allows inheriting data from one super class and then API (and now in 1.8 functionality) from any number of interfaces.
- Rust: realizes inheritance of any data is bad, and only allows for any number of Trait's (interfaces) to be implemented on types.
There's a difference, and ultimately it's that you're not carrying data via the inheritance chain, only functions. The benefit here being that it's much easier to reason about a type and what data it has that determines how it works in the broader system. This allows for very explicit inclusion of data from other types, as opposed to implicit inclusion with C++ and/or Java. In the end moving away from data inheritance leads to fewer mistakes and easier understanding of the code.
With Java 1.8 you can ask the same thing about Java and the answer would be the same.
I didn’t mean to suggest that it’s idiomatic in C++ to create strange inheritance graphs, anyone who’s done it once realizes it’s a bad pattern. The issue is more that the language -allows- it.
Composition over inheritance solves only certain language problems where limitations of expressive power make the code less transparent or flexible, but it’s not a solution for decomposition. The problems with properly used inheritance arise only in sufficiently old and evolved code, because decomposition of one domain does not necessarily describe well another (evolved) domain. Programmers tend to think that every problem in the world should be addressed by better coding, so they try to change their coding practices. In fact, solution here is managerial - throw away the old code and do the decomposition once again, for the new circumstances and new requirements. OOD and inheritance work well, so let’s not blame them for the use cases which they cannot solve.
100% this. We're never going to built a perfect sand castle.
IMHO - There are many managerial short comings in software development that lead to abuse of legacy design. OOP by design allows you to abstract and ignore not only original implementation decisions but reflections of those decisions in the architecture. Polymorphism makes sense when it's the classic ICat: IAnimal example but so often it _becomes_ IHouseFly : IAnimal because all the contracts expect IAnimal and the deadline is.....tomorrow.
I personally don't have a good solution given the pragmatic counter argument that the ROI on a system which is cheap to develop but is patched over 5 years may be equal to or cheaper than one that is expensive to develop and _still_ needs to be patched over it's 5 year lifetime. Let's call this the used car problem. A new car is more reliable but now that used cars are reliable _enough_ it's harder to convince people _not_ to gamble with a lower upfront cost.
I absolutely _love_ Rust and the code I'm written feels bullet proof. No idea how long it would take a team of my C# peers to be even 1/10th as productive in Rust/Go as they are in Visual Studio and C#.
Indeed! This has always mystified me. Why is it so easy to implement what is now considered to be an anti-pattern (inheritance) when it's so boilerplate and annoying to implement composition? Why does C# not have language support for delegating members? Why do I have to buy and use ReSharper just to generate all that boilerplate? It's a constant battle talking to "just get it done" developers about why inheritance is bad.
Inheritance is only considered an anti-pattern by some.
Since the 90's, any good CS book about OOP paradigms had discussions about is-a and has-a and how to make the best use of each, depending on the desired application architecture.
The thing is, such books usually aren't taught on bootcamps.
Inheritance isn’t an anti-pattern in academia. I do part time work as an examiners for CS students, and I see their car/animal examples everywhere in introductionary courses.
When we have interns, they’ll sometimes build things with inheritance. So it’s certainly still a thing.
I’ve yet to see a real world use of it, where you wouldn’t have been better of not using it though. My real world is the relatively boring world of enterprise public sector software, however, and maybe I’m simply oblivious to where inheritance might be worthwhile.
That is the thing, the architect should have a proper understanding of is-a and has-a relations and apply them appropriately.
Initially, VBX only allowed for composition as well, COM introduced interface inheritance with delegation, when one wanted to override a couple of methods, but not the remaining several ones.
And now UWP offers mechanisms to do implementation inheritance in COM, because everyone got tired to write delegating code for is-a relations.
Inheritance and composition are both tools, it is up to each one to learn how to use them appropriately.
Many languages (thinking c#, Java, c++) say they promote composition over inheritance but yet make inheritance so mmuch easier to use.