> This contrasts inheritance as a “white box” form of reuse, because the inheriting class has full visibility over the implementation details of the inherited class; with composition as a “black box” form of reuse, because the composing object only has access to the interface of the constituent object.
So, we just need devs to stop trying to be overly clever? I can get behind that, “clever” devs are just awful to work with.
I’ll be honest. I don’t really understand the point of this article. Maybe that’s just a preference thing. The philosophy behind these abstractions is the least interesting part of the question for me. What problems do these various methods of polymorphism solve and create? What solutions do they enable or prevent? That’s the only part that matters. But citing some discussion about the philosophy behind the theory from 40 years ago is not particularly enlightening. Not because it’s not relevant. But because we have 40 years more experience now and dozens of new languages that have different takes on this topic. What has been learned and what has been discovered?
It really helps me to think of it all as extensive metaphors. Math included. The point is to tell an active story using symbols as metaphorical representations of something. With a lot of assumed language implied (through teachings) by choices of naming things. (As a fun example, don't focus on the name Algebraic if you aren't going to lean in on grade school algebra for things.)
That said, I think this is also a good way to approach framing things. Agreed that the idea of "prefer composition" is often a thought termination trick. Instead, try them both! The entire point of preferring one technique over the other is that it is felt to give more workable solutions. If you don't even know what the worked solution would look like with the other technique, you should consider trying it. Not with a precommitment that you will make it work; but to see what it illuminates on the ideas.
Arguably the answer is “When Barbara Liskov invented CLU”. It literally didn’t support inheritance, just implementation of interface and here we have her explaining 15 odd years later why she was right the first time.
I used to do a talk about Liskov that included the joke “CLU didn’t support object inheritance. The reason for this is that Barbara Liskov was smarter than Bjarne Stroustrup.”
There are days I hate the mapping of plain English terms of art over actual in-language effects.
Considering sets, if something is, in set terms a specific subset with a defining membership or characteristic of a definable superset, representing that at compile time effects a hard constraint which honours the set Venn diagram.
If that set/subset constraint doesn't exist then you have to ask yourself if applying a compile time constraint is appropriate.
i think inheritance got a bad name due to abuse of multiple inheritance and overly fragile base classes in c++ (and maybe java) codebases of the 90s and early 00s.
it's mentally satisfying to create a beautiful class hierarchy that perfectly compresses the logic with no repetition, but i think long term readability, maintainability and extensibility are much better when inheritance is avoided in favor of flat interfaces. (also easier to turn into rpcs as all the overcomplicated object rpc things of the 90s were put to bed).
In mainstream/SV coding, I would say the scales just barely tipped toward composition in the late 10s... There are plenty of programmers still completely oblivious, the inertia is huge. Plus the swing back is too strong, inheritance is very powerful, just not as generic as originally thought.
"Composition" is a word that can mean several things, and without having read the original source I never really understood which version they mean. As a rule, I've always viewed "composition" as "gluing together things that don't know necessarily know about each other", and that definition works well enough, but that doesn't necessarily eliminate inheritance.
So then I start thinking in less-useful, more abstract definitions, like "inheritance is vertical, composition is horizontal", but of course that doesn't really mean anything.
And at some point, it seems like I just end up defining "composition" to mean "gluing together in a way that's not inheritance". Again, not really a useful definition.
Article author here. Your idea "gluing together things that don't know necessarily know about each other" is basically what the GoF book means: composition is "this object has a reference to that object and uses its public API". They don't mean "this object ontologically contains an instance of that object" in the sense that a car "has" an engine, which is a narrower definition of composition that people frequently use.
It's that broader version of composition—particularly in its extreme realization, delegation—that underlies a lot of the behavioral patterns in the book. For example, the State and Strategy patterns boil down to "this object relies on another object to fill in the behavior here, and there are ways to choose what that other object is", which is something it's easy to arrange with subclassing and the only point of the pattern is to avoid subclassing.
Hierarchy (and thus "inheritance") is a way to express that several different things share the same quality. They are different in general, but same in some way. It is a very natural way for people to express such a thing and no wonder it is so widespread. But it is not the only way nor the general way, of course.
Composition is not an opposite to inheritance. An opposite would be something like:
Message A ( ... ):
Type B: { ... }
Type C: { ... }
Or, if the body of the method is same ("a parent method"):
Type B, Type C:
Message A ( ... ): { ... }
Here we do not give A and B places in the hierarchy but merely say they respond to the same message or that even the procedure is same.
I do not know if any meaningful and systematic alternative to a hierarchical way exists in any programming notations. Interface spec is a partial way, but that's all. (I know only a few notations, of course).
Each has its place. There's some things that inheritance makes possible, and some things that are best handled by composition. I use both, quite frequently.
It Depends™.
Composition can add a lot of complexity to a design, and give bugs a lot more corners to hide in, but inheritance can be such a clumsy tool, that it just shouldn't be used for some tasks.
That goes for almost everything in software. Becoming zealous about "The Only Correct Way" can be quite destructive.
the article seems to be digging into justifications for using inheritance. one thing I've heard and it seems to work is inheritance is ok for interfaces but usually not good for implementations.
An important point not mentioned by the article is that of "co-recursion" with inheritance (of implementation).
That is: an instance of a subclass calls a method defined on a parent class, which in turn may call a method that's been overridden by the subclass (or even another sub-subclass in the hierarchy) and that one in turn may call another parent method, and so on. It can easily become a pinball of calls around the hierarchy.
Add to that the fact that "objects" have state, and each class in the hierarchy may add more state, and modify state declared on parents. Perfect combinatory explosion of state and control-flow complexity.
I've seen this scenario way too many times in projects, and worse thing is: many developers think it's fine... and are even proud of navigating such a mess. Heck, many popular "frameworks" encourage this.
Basically: every time you modify a class, you must review the inner implementation of all other classes in the hierarchy, and call paths to ensure your change is safe. That's a horrendous way to write software, against the most basic principles of modularity and low coupling.
Gameplay logic inherently leans more towards composition, with a little hint of inheritance.
You can have players and monsters, which are all types of "characters" or "units", which is inheritance, but instead of having a separate FlyingPlayer and a separate FlyingMonster, which use the same code for flight, you could have a FlyingComponent, which is composition.
I've been going all in on composition and it's amazing for quickly implementing new gameplay ideas. For example, instead of a monolithic `Player` class you could have a `PlayerControlComponent` then you can move that between different characters to let the player control monsters, drones, etc.
Imagine instead of only Pac-Man being able to eat the pills, you could also give the ghosts the `PillEaterComponent` in some crazy special game modes :)
I've also been fantasizing about a hypothetical language that is built from the ground up for coding gameplay, that doesn't use the word "class" at all but something else that could be a hybrid of inheritance+composition.
How about not favoring anything. There are many paradigms and each one has its place. Franky I do not really understand why do developers fight these religious wars about languages, frameworks etc.
I never liked inheritance. It seems like something that works well in a world where you assume things don’t evolve rapidly. It also feels like it adds mental debt—every new thing needs to comply with old things to stay compatible. Every update has to take into account how old components are working. Probably, the static nature helps big teams and big companies. But I’ve found that some duplicated code is way easier to deal with, especially now that LLMs can generate new code so quickly.
54 comments
[ 2.5 ms ] story [ 46.2 ms ] threadSo, we just need devs to stop trying to be overly clever? I can get behind that, “clever” devs are just awful to work with.
https://news.ycombinator.com/item?id=45845505
That said, I think this is also a good way to approach framing things. Agreed that the idea of "prefer composition" is often a thought termination trick. Instead, try them both! The entire point of preferring one technique over the other is that it is felt to give more workable solutions. If you don't even know what the worked solution would look like with the other technique, you should consider trying it. Not with a precommitment that you will make it work; but to see what it illuminates on the ideas.
I used to do a talk about Liskov that included the joke “CLU didn’t support object inheritance. The reason for this is that Barbara Liskov was smarter than Bjarne Stroustrup.”
Considering sets, if something is, in set terms a specific subset with a defining membership or characteristic of a definable superset, representing that at compile time effects a hard constraint which honours the set Venn diagram.
If that set/subset constraint doesn't exist then you have to ask yourself if applying a compile time constraint is appropriate.
Inheritance is just a more deeply integrated form of composition which puts the inherited parts on equal footing with the new parts.
That reduces certain indirections and frictions, which is sometimes useful when making things out of other things.
it's mentally satisfying to create a beautiful class hierarchy that perfectly compresses the logic with no repetition, but i think long term readability, maintainability and extensibility are much better when inheritance is avoided in favor of flat interfaces. (also easier to turn into rpcs as all the overcomplicated object rpc things of the 90s were put to bed).
If you're adding a device for navigation that could be used by other things, go for composition.
Inheritance might be OK for formally finite domains but I can’t envision other cases where it should be favored.
So then I start thinking in less-useful, more abstract definitions, like "inheritance is vertical, composition is horizontal", but of course that doesn't really mean anything.
And at some point, it seems like I just end up defining "composition" to mean "gluing together in a way that's not inheritance". Again, not really a useful definition.
It's that broader version of composition—particularly in its extreme realization, delegation—that underlies a lot of the behavioral patterns in the book. For example, the State and Strategy patterns boil down to "this object relies on another object to fill in the behavior here, and there are ways to choose what that other object is", which is something it's easy to arrange with subclassing and the only point of the pattern is to avoid subclassing.
Composition is not an opposite to inheritance. An opposite would be something like:
Or, if the body of the method is same ("a parent method"): Here we do not give A and B places in the hierarchy but merely say they respond to the same message or that even the procedure is same.I do not know if any meaningful and systematic alternative to a hierarchical way exists in any programming notations. Interface spec is a partial way, but that's all. (I know only a few notations, of course).
It Depends™.
Composition can add a lot of complexity to a design, and give bugs a lot more corners to hide in, but inheritance can be such a clumsy tool, that it just shouldn't be used for some tasks.
That goes for almost everything in software. Becoming zealous about "The Only Correct Way" can be quite destructive.
That is: an instance of a subclass calls a method defined on a parent class, which in turn may call a method that's been overridden by the subclass (or even another sub-subclass in the hierarchy) and that one in turn may call another parent method, and so on. It can easily become a pinball of calls around the hierarchy.
Add to that the fact that "objects" have state, and each class in the hierarchy may add more state, and modify state declared on parents. Perfect combinatory explosion of state and control-flow complexity.
I've seen this scenario way too many times in projects, and worse thing is: many developers think it's fine... and are even proud of navigating such a mess. Heck, many popular "frameworks" encourage this.
Basically: every time you modify a class, you must review the inner implementation of all other classes in the hierarchy, and call paths to ensure your change is safe. That's a horrendous way to write software, against the most basic principles of modularity and low coupling.
You can have players and monsters, which are all types of "characters" or "units", which is inheritance, but instead of having a separate FlyingPlayer and a separate FlyingMonster, which use the same code for flight, you could have a FlyingComponent, which is composition.
I've been going all in on composition and it's amazing for quickly implementing new gameplay ideas. For example, instead of a monolithic `Player` class you could have a `PlayerControlComponent` then you can move that between different characters to let the player control monsters, drones, etc.
Imagine instead of only Pac-Man being able to eat the pills, you could also give the ghosts the `PillEaterComponent` in some crazy special game modes :)
I've also been fantasizing about a hypothetical language that is built from the ground up for coding gameplay, that doesn't use the word "class" at all but something else that could be a hybrid of inheritance+composition.