I think that composition is absolutely better than inheritance except for one thing: boilerplate. The issue is that boilerplate is kind of important.
You don't want to litter your code with "f150.ford.car.vehicle.object.move(50, 50)". You can and should re-implement "move" so that you only have to call "f150.move(50, 50)", but that still requires boilerplate, just in the "F150" class.
Often you have class containing all of the functionality of another class, except a bit more functionality. You can always use composition but this happens so often you're creating a lot of boilerplate.
You could develop some other "syntax sugar" to replace inheritance. Maybe Haskell's type-classes are better (although they also kind of use inheritance, since there are subclasses). But chances are you'll go back to something like inheritance, because it's very useful very often.
COM solves this with delegation, where objects can only implement the methods that they care about and delegate everything else to the aggregated type, which provided the full interface.
However, depending on which stack one is using (VB 6, .NET, MFC, ATL, WRL, WinRT), the amount of boilerplate to deal with the runtime differs.
This doesn't inhherently have anything to do with inheritance. Delegation is the compositional solution to this problem and some languages do have built in sugar for that.
It usually looks something like:
class F150(@delegate private val underlying: Car) { ... }
class F150(private val underlying: Car) : Car by Underlying { ... }
// etc
With it, you F150 can say it implements the "movable" interface, just buy stating which field it contains that implements it, and the you can run "f150.move"
I'd like languages to have some kind of "delegate" functionality, where you can just delegate names to point to nested names without screwing around with ownership - it would just act like a symlink. The scope of that action is limited and clear (and easy for your IDE to understand), and it's explicit that the subclass is still the "owner" of that property, which makes the whole thing a lot easier to navigate.
E.g. something like:
class MyClass:
def __init__(self, member_class):
self.member_class = member_class
# Delegate one member
delegate move member_class.position.move
# Delegate all members
delegate * subclass.position.*
C++ can do something something like this (at compile time) in its -> operator (ancient feature, long before C++98 was standardized).
obj->foo()
will expand into enough -> dereferences until a foo is found. For instance suppose the object returned by obj's operator ->() function doesn't have a foo member, but itself overloads ->. Then that overload will be used, and so on.
The reason I'd like the construct is because it's explicit - intent (and the scope/limit of your intent) is encoded in what you create. It's clear you intend to do nothing with that name except symlink to the nested member, so the reader doesn't have to anticipate other behaviour (and can't accidentally do something else with it). Generic assignment doesn't convey the same restricted intent, and it doesn't carry those guard rails.
Really though it's a structure that only makes sense in strongly typed languages, so I probably shouldn't have used Python to illustrate the idea.
I'd like to point out that the article isn't disagreeing with you. It's saying inheritance is a dangerous interface for other users of your code (across packages is their terminology). So, if you write a library, maybe don't design it around extending classes. This is a much milder stance than the title implies, and seems pretty reasonable to me.
That's not general [implementation] inheritance, it's just delegation. The problematic, non-compositional feature of implementation inheritance is something known as open recursion, viz. the fact that every call to an overridden method like .move(...) - importantly, even a call that's merely a private implementation detail of some code in the base class - goes through an implied dispatch step that introduces a dependency on the actual derived class instance that the base-class method is being called on. This creates the well-known "fragile base class" problem since method calls to these possibly-overridden methods are relying on fragile, unstated invariants that might be broken in the derived classes, or altered in future versions of the base class.
For the same reason, I'm not so absolutist about DRY. Having the most elegant codebase also often means the codebase that's hardest to work on, and it's often better to clean things up afterwards once you know how things will be structured.
This question determines if you need to be DRY or not:
"If [some fact] in the code base needs to change, how many places would we have to change it in?"
If the answer is > 1, you have a very good DRY case. Otherwise, when [some fact] changes, it will probably not be changed in one of the places, and the system will be broken.
This often coincides with having an "elegant codebase", but that's not the most important part.
For a living codebase, nowadays my general rule of thumb is to consciusly duplicate code until it covers 3 different cases, and only then refactor (unless the DRY way is as fast and obvious).
It takes more than that to yield spaghetti and a lot of time is saved on premature generalization. Plus the generalization is often way more straightforward once the explicit cases are already implemented.
The original formulation of DRY was "Every piece of knowledge must have a single, unambiguous, authoritative representation within a system," which in import winds up pretty close to what you have here.
Recently (last month basically), i rewrote my code (i'm leaving soon and i want my coworkers/successors to have success improving on what i've done).
I followed every principle of good code, except one, DRY. I tried to make generic parts to connectors, because they do have similarities. But this is a work of at least a year, and the price to make it generic was increasingly more complex configuration files (Just the pagination alone added 3 variables for two different APIs, and the number of app i am supposed to interact with should grow to ~40). I decided after a few days of reflexion that the idea was not that dumb in principle, but unworkable in my case, and decided that one connector for one API, even with a lot of repetition.
Yeah, I had a few connectors (API clients to different APIs with some business logic wrappers to handle low level stuff) and decided that they should share code with a generic connector interface, and then when one of them changed it was pretty painful untangling it. There is a tradeoff between getting stuff done and abstraction engineering, and there are costs to premature abstraction engineering.
Go solves this with problem with embedding. If a type is imbedded inside of a struct and has its own methods, those methods are implicitly available on the new struct.
You don't want to litter your code with "f150.ford.car.vehicle.object.move(50, 50)". You can and should re-implement "move" so that you only have to call "f150.move(50, 50)", but that still requires boilerplate, just in the "F150" class.
Often you have class containing all of the functionality of another class, except a bit more functionality. You can always use composition but this happens so often you're creating a lot of boilerplate.
You could develop some other "syntax sugar" to replace inheritance. Maybe Haskell's type-classes are better (although they also kind of use inheritance, since there are subclasses). But chances are you'll go back to something like inheritance, because it's very useful very often.