12 ms·
When did people favor composition over inheritance?
- gishh 1y ago> 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.
- Jtsummers 1y agoLooks like this one was reupped from a week or so ago, there was another submission with three comments too: https://news.ycombinator.com/item?id=45845505 https://news.ycombinator.com/item?id=45845505
- skywhopper 1y agoI’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?
- jankcorn 1y agoI usually think of the ideas behind "composition" as "how do I assist a future developer to replace the current (exported) implementation of a type with a new one by restricting external visibility of its internal implementation through the use of private methods and data". In "inheritance", it often feels like the programmer's mindset is static, along the lines of "here is a deep relationship that I discovered between 2 seemingly unrelated types", which ends up being frozen in time. For example, a later developer might want to make a subtle innovation to the base type; it can be quite frightening to see how this flows though the "derived" types without any explicit indiction. Of course, YMMV, but I think of "composition" as "support change" and "inheritance" as "we found the 'correct way to think about this' and changes can be quite difficult". Since I think that the key to building large systems handling complex requirements is 'how do we support disciplined change in the future' (empowering intellectual contributions by later generations of developers rather than just drudge maintenance).
- taeric 1y agoIt 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.
- moomin 1y agoArguably 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.”
- zelphirkalt 1y agoI mean, it's not that hard to understand, why composition is to be preferred, when you could easily just use composition instead of inheritance. It's just that people, who don't want to think have been cargo-culting inheritance ever since they first heard about it, as they don't think much further than the first reuse of a method through inheritance.
- gf000 1y agoNo, it's not a complete replacement for inheritance.
- zelphirkalt 1y agoNor did I claim so.
- ardit33 1y agoComposition folks can get very dogmatic. I have some data types (structs or objects), that I want to serialize, persist, and that they have some common attributes of behaviors. In swift I can have each object to conform to Hashable, Identifiable, Codabele, etc etc... and keep repeating the same stuff over and over, or just create a base DataObject, and have the specific data object inherit it and just . In swift you can do it by both protocols, (and extensions of them), but after a while they start looking exactly like object inheritance, and nothing like commposition. Composition was preferred when many other languages didn't support object oriented out the gate (think Ada, Lua, etc), and tooling (IDEs) were primitive, but almost all modern languages do support it, and the tooling in insanely great. Composition is great when you have behaviour that can be widely different, depending on runtime conditions. But, when you keep repeating yourself over and over by adopting the same protocols, perhaps you need some inheritance. The one negative of inheretance is that when you change some behaviour of a parent class, you need to do more refactoring as there could be other classes that depend on it. But, again, with today's IDEs and tooling, that is a lot easier. TLDR: Composition was preferred in a world where the languages didn't suport propper object inheretance out of the gate, and tooling and IDEs were still rudemmentary.
- ggm 1y agoThere 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.
- kazinator 1y agoI went out of my way to implement inheritance and then make it multiple. Of course I'm going to use it. 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.
- nrhrjrjrjtntbt 1y agoYou can get Liskov from interfaces too. I rarely (like once in a career) need inheritance.
- a-dub 1y agoi 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).
- shadowgovt 1y agoRpcs really can't be understated in terms of the effect they had on classes and inheritance. While in theory it should be straightforward to ship instance state over a wire, in practice most languages have no built-in support for it (or the support is extremely poor in the general case; I remember my first experiments with trying to ship raw Java objects over the wire using the standard library tools back in the early 2000s, and boy was that incredibly inefficient). Additionally, the ability to attach arbitrary methods to instances in some languages really complicates the story, and I think fundamentally people are coming around to the idea that the wire itself is something you have to be able to inspect and debug so being able to understand the structure in transit on the wire is extremely important. Classes and their inheritance rules make exactly the wrong things implicit for this use case.
- deleted 1y ago[deleted]
- aussieguy1234 1y agoIf it's a car with extra wheels, do inheritance. If you're adding a device for navigation that could be used by other things, go for composition.
- binary132 1y agoOnce upon a time inheritance was a way to compose classes out of pieces of orthogonal, general functionality. Is-a Winged, TurbinePowered, Piloted, Aircraft, etc
- oh_my_goodness 1y agoYeah inheritance is just not the point of OO. It’s fine but it’s not what’s really useful.
- mrkeen 1y agoWhat is the point of OO?
- zigzag312 1y agoEncapsulation
- deleted 1y ago[deleted]
- oh_my_goodness 1y agoThe main point is the same as the Dewey Decimal System. Keep things tucked away yet findable. Make a huge code base useful to people who didn't write it themselves.
- raspasov 1y agoWhen someone realized that the inheritance glass castle is doomed to always get shattered upon contact with the real world. Inheritance might be OK for formally finite domains but I can’t envision other cases where it should be favored.
- cturner 1y agoDo you dislike type inheritance? Or only implementation inheritance? My view is that type inheritance is incredibly useful, both for single system programming, and rpc. Whereas implementation inheritance creates brittle systems.
- moi2388 1y agoThe only time I use inheritance is when I have an abstract base class, and several flavours of subtypes, all sealed.
- deleted 1y ago[deleted]
- anacrolix 1y agoIn 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.
- lowbloodsugar 1y agoWhen they put away childish things and read about the SOLID principles. Different time for every engineer.
- bccdee 1y agoSOLID is a childish thing, imo. Very undergrad. "Single responsibility" isn't an especially useful yardstick. If you actually need to decompose a complex piece of logic into modules, the place to start is by identifying areas of high cohesion and separating them into loosely coupled functions. Ideally you can match those up to a DDD-style ubiquitous language, so your code will make intuitive sense to people familiar with the domain. "Does this have one responsibility?" really isn't the right question to ask. The open-closed principle is straight-up wrong. Code should be easy to modify and easy to delete, and you only rarely need to add hooks for extensibility. Liskov substitution is fine, but it has more to do with correctness than cleanliness. Dependency inversion is a source of premature abstraction—you shouldn't open the door to polymorphism until you need to. Interface segregation is good, though. In general, I think SOLID is overly enamoured with the features of object orientation. Objects themselves just aren't that big of a deal. It'd be like making the whole acronym about if-statements. If I were going to make a pithy acronym about legible code, it'd have more to say about statelessness, coupling, and unit tests. It'd reference Ousterhout's idea of deep modules, and maybe say something about "Parse, don't validate," or at least something against null values.
- lowbloodsugar 1y agoThank you for taking the time to reply, instead of just hitting downvote. I feel like if we argued over a beer we’d probably end up agreeing on a lot of things. But let’s start by disagreeing. :-) > "Does this have one responsibility?" really isn't the right question to ask. It’s a great question to ask. As a senior engineer, the answer might be “no”, but there’s a vast difference between code where the answer is “no” because someone made a conscious choice, vs code where nobody even asked the question. Here’s the thing: a compiler and linker can join ten classes into a single executable, but even a senior engineer cannot look at a single class with ten responsibilities and figure out what the fuck is going on. There’s a doc at my company that describes the core function of one particular service. The doc describes the simplest of systems and so you would be surprised to learn that 1) it took me two years of working one the product before I could write it and 2) nobody knew. The reason it took two years was because there were 10 different pathways, and every pathway was just a giant implementation, each written differently, and each, ultimately, doing the exact same fucking thing. But you’d never be sure just by looking at the code. In fact it very much looked like each of these things had very specific things that they did differently. Over two years, while also doing my job of keeping this thing running and adding features, I refactored the thing to be SOLID. In doing so, demonstrated that they all do the exact same thing. We haven’t finished refactoring everything, but we do now test all the pathways with a parallel implementation that verifies 80 classes and 500 instances at runtime with one class and ten instances. I work on software that you and most people on planet earth with at least a mobile phone are using in one way or another. I have made many pieces of this system better by evolving a clusterfuck of cohesion into a system that is easy to reason about, maintain and evolve - by apply SOLID principles. I’m currently working on a package used by over 1,000 services. The most pain has been caused by previous iterations ignoring the open-closed principle. As you say, “easy to modify and delete”. A stronger rule, which perhaps you’re alluding to, is don’t allow any extension at all, and just expose only interfaces. In that sense I could agree open-closed principal is moot, but it’s moot for taking its argument to the logical conclusion. I am also a fan of DDD, and for the reasons you allude to: the second half of the book is more about communicating in a large engineering organization.
- catigula 1y agoIt takes about 2-3 years of experience in current enterprise scale to deeply realize that inheritance fundamentally doesn't work.
- mmis1000 1y agoIt depends though. Learning what things don't actually work like the textbooks says is the key to level from junior to senior. Some people never get it, some got it quickly.
- tombert 1y ago"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.
- crystal_revenge 1y agoI find the Monoid/Semigroup typeclass pretty concisely captures what is generally meant by "composition" in the minimal sense. > As a rule, I've always viewed "composition" as "gluing together things that don't know necessarily know about each other" The extension to this definition given the context of Monoids would be "combining two things of the same type such that they produce a new thing of the same type". The most trivial example of this is adding integers, but a more practical example is function composition where two functions can be combined to create a new function. You can also think of an abstraction that let's you combine two web components to create a new one, combining two AI agents to make a new one, etc. > "inheritance is vertical, composition is horizontal", but of course that doesn't really mean anything. This can actually be clearly defined, what you're hinting at is the distinction between sum types and product types. The latter of which describes inheritance. The problem with restricting yourself to only product types is that you can only add things to an existing thing, but in real life that rarely makes sense, and you will find yourself backed into a corner. Sum types let you have much more flexibility, which in turn make it easier to implement truly composable systems.
- tombert 1y agoI actually knew most of that (I've done a lot of Haskell). I don't really disagree with what you said, but I feel like like you eliminate a lot of stuff that people would consider "composition" but aren't as easily classified in happy categories. For example, a channel-based system like what Go or Clojure has; to me that is pretty clearly "composition", but I'm not 100% sure how you'd fully express something like that with categories; you could use something like a continuation monad but I think that loses a bit because the actual "channel" object has separate intrinsic value. In Clojure, there's a "compose" function `comp` [1], which is regular `f(g(x))` composition, but lets suppose instead I had functions `f` and `g` running in separate threads and they synchronize on a channel (using core.async)? Is that still composition? There are two different things that can result in a very similar output, and both of which are considered by some to be composition. So which one of these should I "prefer" instead of inheritance? Of course this is the realm of Pi Calculus or CSP if you want to go into theory, but I'm saying that I don't think that there's a "one definition to rule them all" for composition. [1] https://clojuredocs.org/clojure.core/comp https://clojuredocs.org/clojure.core/comp
- Mikhail_Edoshin 1y agoHierarchy (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).
- ChrisMarshallNY 1y agoEach 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.
- CharlieDigital 1y agoSame way I see it. Objects and inheritance are good when you need big contracts. Functions are good when you want small contracts. Sometimes you want big contracts. Sometimes you want small contracts. Sometimes the right answer is to mix and match.
- strix_varius 1y agoSure, but "favor x over y" or, put another way, "use y only if x is unsuitable" is compatible with this. Nothing in "prefer composition over inheritance" says that composition is the only correct way.
- ChrisMarshallNY 1y agoNo, but I often see folks use it as a bludgeon for dogma.
- bccdee 1y agoI dunno. It's easy to say, "there are trade-offs, it depends" any time two things are compared, and it's never entirely untrue. However, sometimes one option is just generally worse than the other. I'm not saying it's malpractice to use inheritance or anything, but it's a tool I definitely hesitate to reach for. Go and Rust removed inheritance entirely, and I'd say those languages are better-off without it.
- ChrisMarshallNY 1y agoThere’s definitely stuff that it enables. I’ve been writing software since before it was a thing, and it was almost magic, when I first learned about it. I also saw why it fell from grace, but I already knew, by then, that it was no panacea. I learned, on my own, that composition was often a better pattern, and I learned that, back in the 1980s. Not worth arguing about, but I do find absolutism to be almost offensive, and there’s a damn lot of that, in software development.
- filloutform 1y agothe 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.
- ferd 1y agoAn 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.
- kccqzy 1y agoThis is only the case when the language does not distinguish between methods that can be overridden versus those that cannot. C++ gives you the keyword "virtual" to put in front of each member function that you want to opt into this behavior, and in my experience people tend to give it some thought on which should be virtual. So I rarely have this issue in C++. But in languages like Python where everything is overridable, the issue you mention is very real.
- ferd 1y agoGood point. In Java and many other languages you can opt out instead... which might make a big difference. Is it more of a "cultural" thing?... again, many frameworks encourage it by design, and so do many courses/tutorials... so those devs would be happy to put "virtual" everywhere in C++
- lepicz 1y agoheh, i have seen programmers using virtual everywhere, because they were lazy to use declspec(dll_export) on windows system :)
- Razengan 1y agoGameplay 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.
- baklazan 1y agoYou can do the very same with inheritance, where `Player` and `Monster` inherit `Flying`
- Razengan 11mo agoWhat about walking monsters? Another inheritance branch for those? And other subtrees for other effects? What about making those effects temporary?
- DonHopkins 1y agoEntity Component System: https://en.wikipedia.org/wiki/Entity_component_system https://en.wikipedia.org/wiki/Entity_component_system
- Razengan 1y agoYeah, but all current languages still have to wrangle ECS into an inheritance-first architecture: `class` etc. Would be nice if something like Swift's "Protocols" could be used in a more dynamic way, at the code level.
- FpUser 1y agoHow 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.
- bccdee 1y ago> There are many paradigms and each one has its place. That's a thought-terminating cliché. The argument against inheritance has been laid out pretty clearly. It's reasonable to rebut that argument. It's not reasonable to say, "you shouldn't criticize inheritance because Everything Has Its Place." Everything does not have its place. Sometimes we discover that something is harmful and we just stop using it.
- bigstrat2003 1y ago> Sometimes we discover that something is harmful and we just stop using it. And that is not remotely the case here. So yeah, there are many paradigms and each has its place.
- bccdee 1y ago> And that is not remotely the case here. Isn't it? People have written extensively about why we should prefer composition to inheritance, and you haven't mounted any defence of inheritance beyond the thought-terminating cliché that it "has its place."
- FpUser 1y agoI use both where choosing what I believe is appropriate for particular case. Frankly I do not give rat's ass about what "People have written extensively". From what I read most of it sounds like spoken by politician: look Jimmy, someone can do a bad thing with it. Well fuckin don't do a bad thing. So much over very simple and primitive thing: John HAS a key vs dog IS an animal. Both are valid and proper. >"you haven't mounted any defense" Why would I bother. It does not need a defense. It is like do not use Java because it encourages FactoryFactoryFactory, 20 level of abstraction etc. Well it does not. Architecture astronauts do it and I am not one of those
- Frannky 1y agoI 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.
- khannn 1y agoWhen I first took an object oriented programming class it was all about inheritance so that's what I tried to use for everything. Then I started writing real programs and realized that inheritance sucked then finally found the succent "Favor composition over inheritance".
- bccdee 1y agoWhat I like about the modern¹ approach (interfaces + composition) is that it cleanly untangles polymorphism from behaviour-sharing. When you inherit from a parent class, you have to be careful to only override methods in ways that the parent expects, so the parent's invariants aren't broken². There's a whole additional set of keywords (private/protected/final) meant to express these parent-child contracts. With interfaces + composition, those are unnecessary: You can compose an object and use it however you want; then, if you want the wrapper object to uphold the inner object's contract, you can additionally implement an interface to formalize that. The behaviours you use and the polymorphic guarantees you make are totally separate. Inheritance mixes these ideas together and ends up worse for it. Not only the modern approach is simpler, it's more powerful: (1) Polymorphic extension (i.e. extending a polymorphic parent class at runtime) is doable, and (2) multiple inheritance is a non-issue. [1]: I call it "modern" because newer languages like Go and Rust have eliminated inheritance in favour of exclusively using interfaces/traits. [2]: See the fragile base class problem.
- musicale 1y ago> modern¹ approach (interfaces + composition) Smalltalk protocols and message categories were a step toward this (for example you could classify messages as implementing a particular interface, such as the collection or stream protocols), but Smalltalk lacked the type and interface checking supported by Java and other languages.
- igouy 1y agoSo "Adding Dynamic Interfaces to Smalltalk" https://www.jot.fm/issues/issue_2002_05/article1/index.html https://www.jot.fm/issues/issue_2002_05/article1/index.html
- joeyguerra 1y agoIn 2006 when changing code that had lots of inheritance.
- lincpa 1y ago[dead]
- pfannkuchen 1y agoCertainly not second born musician sons under primogeniture.
- rramadass 1y agoI am always bemused when i see articles like these. Do people not have an understanding of fundamental Software Engineering principles from OGs like Parnas/Liskov/etc.? The fundamental idea is that of Abstraction which can be defined as the discovery/invention of "higher-level concepts" from more primitive "lower-level concepts" and then reasoning and manipulating at the higher-level. This abstraction is based on structure and/or behavioural attributes. In order to manage the complexity inherent in the building of large systems certain fundamental aspects were identified as highly desirable. They are Separation-Of-Concerns, Modularization, Reuse and Information-Hiding. The crucial point to understand is that Abstraction does not imply any of the above aspects! A good example are Mathematical Abstractions. But because for Software we desire the above aspects for our system-as-a-whole we learn to combine them with our Abstractions. This is why we have so many different styles of Programming (i.e. Imperative/OO/Functional/Logic/etc.). Viewed in the above light the relation between Inheritance and Composition becomes clear. They are just different ways of emphasizing different combinations of the above aspects for your abstractions based on your design needs. References: 1) Software Fundamentals: Collected Papers by David L. Parnas. 2) Program Development in Java: Abstraction, Specification, and Object-Oriented Design by Barbara Liskov and John Guttag. 3) Multi-Paradigm Design for C++ by James Coplien.
- rifty 11mo ago>Do people not have an understanding of fundamental Software Engineering principles from OGs like Parnas/Liskov/etc.? I believe this hints at a major culprit contributing towards the sentiment and use of OO in practice over the years. I'm going to say no people don't. Even if someone goes through education, it won't often require engaging with multiple formative past perspectives at length.. even though there is real value to these musings by comparing and contrasting them altogether first hand.
- rramadass 11mo agoIt is ironical that today when you have a vast body of knowledge literally at your fingertips people seem to have no curiosity nor drive to figure things out for themselves starting from the original sources. Everything seems to be mere parroting and inane opinion pieces with no concept of nuance. People seem to demand a black-and-white (i.e. tell me what is right and wrong) cut-and-dried (i.e. tell me what is settled and decided) answer to all their doubts/queries. They seem to not understand that nothing in Software Engineering is a definite law but are simply reasonings based on empirical deductions resulting in agreed upon principles/heuristics. Thus meta-principles drive abstractions resulting in concepts which are then expressed via language features. Now, a language feature expresses only those aspects of the fundamental concept that its designer decided as "correct" (i.e. his/her perspective) which is never its full generality eg. Inheritance expression in various languages. So people need to ask themselves "What is the fundamental concept that i am trying to express via this feature?" and slowly work upwards from specific features to general concepts to even more general meta-principles.
- morshu9001 1y agoIt's been settled multiple times that a relational DB is your default choice, as opposed to an object DB. Feels like the same lesson applies to OOP. Objects are ok when you have a simple bag of properties, but otherwise begin to distract from what you really want to model. And I guess composition is more analogous to relations.
- cat_plus_plus 1y agoIs your code simple? Then use whatever helps you finish it fast and rewrite later if needed. Or is it complicated? Then don't rely on any canned advice. If you are implementing a virtual machine on an embedded chip, maybe parallel arrays and gotos are the way to go, nobody except you knows. Everything else is just overpaid senior architects trying to justify their own existence by not allowing working code to be merged.
- krackers 1y agoThere's a chapter in Effective Java that deals with this, and it cites https://www.cs.tufts.edu/comp/150CBD/readings/snyder86encapsulation.pdf https://www.cs.tufts.edu/comp/150CBD/readings/snyder86encaps... so at least as early as 1986
- zigzag312 1y agoIn a way, this is similar to tags vs folders. Folders are hierarchical way of organizing, akin to inheritance and tags are compositional way of organizing. I'm kind of waiting for any language to invent some sort of #hashtag interfaces to define contracts :)
- anon291 1y agoInheritance is not a fundamental concept of anything. Inheritance is just composition with syntactic sugar. The semantic meaning was always composition. Oop is a mistake. Rust and pythons explicit self passing and turning of the dot operator into simple syntactic sugar is the correct approach. We should just stop teaching everything related to this in universities and go back to fundamentals.
- zozbot234 1y agoImplementation inheritance is not just composition. Composition on its own does not allow for open recursion (implementing methods that were called on a base class in a derived class, via an in-built dispatch step), whereas inheritance does.
- anon291 1y agoA virtual table and virtual dispatch are orthogonal to inheritance. Haskell let's you do the former without the latter. I agree that syntactic sugar for virtual dispatch is a nice language feature because it is tedious to do by hand
- grahar64 1y agoI once worked with a library that had such a deep inheritance tree, only for ontological purposes, that I was always confused as to where anything was actually implemented. I decided to squash the layers and found almost every method was overrode two or three times. That was the project that I turned against inheritance, it was 2009, project was written in Java 1.4
- maxloh 1y agoI found the following video from CodeAesthetic explains this concept really well. https://youtu.be/hxGOiiR9ZKg https://youtu.be/hxGOiiR9ZKg
- mmis1000 1y agoWhen they find inheritance is actually worse at describe the concept though. With composition you no longer need to implement whatever interface and bridge the implementation by proxy or whatever. You are also not limited to what parent class have (while you can still add all components that parent have to children if you need). Interface and proxy is just composition but worse in my opinion
- Blackarea 1y agoGrepping "extends" over a new codebase is a quick way to see how fucked you are when joining a new project/team.
- mihaic 1y agoI have been using inheritence for 15 years, and have sometimes regretted it and sometimes loved it. It does have actual benefits if you can limit its usage, and don't use the full insanity that languages like C++. I generally dismis people that tell you to always use composition over inheritance without first understanding the problem space, and how it could be modeled.
- jillesvangurp 1y agoOne of the lesser known features in Kotlin is interface delegation. This lets you get away with doing multi class inheritance via composition of a class with a delegate. This kind of blurs the boundaries between inheritance and composition in a useful way. class Foo(internal val _list: MutableList<Int>=mutableListOf()): MutableList<Int> by _list { ... } Here Foo has a _list property that it delegates the implementation of list operations to. You can even do function overrides in the class and interact with the delegate via the _list property. However, messing with internal list state is off limits (a problem with inheritance). val foo = Foo() foo.add(1) Like Java, Kotlin supports single class inheritance. But this provides a way out. When I was researching this stuff in the nineties, I came across some papers about role based programming by a Norwegian called Trygve Reenskaug. That formed a lot of my thinking on this topic a bit. Modern Kotlin and Java look a lot like what he proposed: small interfaces (roles) and classes that implement multiple of these things whose objects can play those roles in different contexts. Go's duck typing (having the operations means it implements the interface) is also cool for this. Traits, mixins, etc. are all variations on this topic that you can find in other languages. Javascript is actually a really interesting languages since it is a prototype based language (inspired by a long forgotten language called Self). It did not have classes for a long time (that's a recent syntactic addition) and you create new objects by copying old ones. And since it is dynamically typed, it has no need for interfaces either.
- gf000 1y agoThough it's important to add that composition is not a complete replacement for inheritance, see the Self problem. (The Manifold project has a good description on it).
- pjmlp 1y agoThat idea is first visible in OOP systems like COM, which depending on the language, or to use a more recent term from WinRT (language projection), exposes that capability. Since COM only allows for interface inheritance, unlike SOM from OS/2 which also did classes, the way to avoid doing from scratch all members, is to compose and delegate all unmodified methods, while implementing only the new ones in an extended interface. MFC, ATL, VB and Delphi provided some mechanisms to make this easier, naturally not at the same level as easyness done by Kotlin. By the way, the same concept is available in Groovy, with @Delegate annotation.
- seanalltogether 1y agoI've been building gui applications for the past 20 years and I couldn't imagine doing it without an inheritance model. There's so much scaffolding needed to build components and combine them into a working view. Sure inheritance can be bad in the data layer because you don't want to handcuff yourself to bad data expectations. But building out views and view controllers, there's a lot of logic you don't want to keep duplicating every time.
- kragen 1y agoGuess what, lots of people have been building GUI applications without views, much less view controllers, for longer than that. Including Squeak, with Morphic.
- simplesocieties 1y agoAnd yet somehow the Zed team managed to do it with gpui and rust.
- seanalltogether 1y agohttps://github.com/zed-industries/zed/blob/main/crates/gpui/examples/input.rs https://github.com/zed-industries/zed/blob/main/crates/gpui/... GPUI is a great example of the insane amount of boilerplate needed to create a component when you don't have inheritance. My guess is that people don't create a lot of individual components in this framework to handle different business cases, and instead overload a single text input component with a million different options. I would hate to untangle a mature app written under those conditions.
- ChicagoDave 1y agoWhen we realized object models were an anti-pattern. Abstract base classes or just regular class hierarchies inherently create tightly-coupled structures. An eventual maintenance nightmare. Modularization was the core principle of DDD and it still holds up 20 years later.
- Surac 1y agoComing from C++ and C# I think interface inheritance is good. But code inheritance is bad. I always try to avoid it. The only times I need to use code inheritance is wen I have to use framework classes that have bug or broken behavior I have to repair. Eg: label control in c# copy its text to clipboard on double click in windows.form
- pjmlp 1y agoGreat article, I though it was going to be yet another one looking at recent trends, however it actually dives into the history of how it came to be, as someone that started learning OOP with Turbo Pascal 5.5 and Clipper 5, before other OOP languages.
- HarHarVeryFunny 1y agoComposition is ultimately more flexible and less constraining than inheritance. It reflects a practical approach of just using the types/classes you need, without having to adopt some project wide OO religion or design philosophy. With C++, no-one needs to be told (even if good advice) to "favor composition over inheritance" - I think most people who have worked with the language for long enough on large enough projects will end up realizing for themselves that this is generally the preferred approach. Inheritance is a specialized tool, best reserved for specialized use cases. It's a bit of a shame that C++ "Concepts" were never adopted, or some other type of compile-time polymorphism, since I think this is often all that is really wanted - a compile time guarantee that two classes will provide the same interface, without forcing them to be related by inheritance.
- tyleo 1y agoMy personal preference for composition over inheritance is that it forces callers to call the owned-object’s methods directly rather than automatically through inheritance. There is more typing/boilerplate but when you read the class file you get a full picture of what’s happening rather than some parts happening automatically in a different file. I like to say that code should be written with a reader bias: the singular writer should do more work if it makes the class more obvious for the multitude of readers. I feel like composition is a good example of that.
- DarkNova6 1y agoThe way I like yo phrase it: concretion over indirection.
- mekoka 1y agoI call it "read-optimized code". Inheritance is biased toward conservative writing. Once your mind becomes so enmeshed with the code base that you can no longer fathom a future where you might fall out of sync with it, inheritance becomes extremely appealing. It's all in your head! You pull the Razzle parent, sprinkle a bit of Dazzle mixin, everything is alchemized into a Fizzle class and abracadabra. Meanwhile newbies in the team have their eyes welling up from having to deal with your declarative mess.
- tyleo 1y agoThat’s a great term and I’ll probably use it instead of mine. Thanks!
- voxleone 1y agoThe split between inheritance-heavy OOP and composition-first OOP really just reflects how software design has shifted toward approaches that handle change better. Inheritance still solves real problems, but for most modern, fast-changing systems, composition usually offers a smoother path. Of course, developers mix and match depending on what the situation calls for. But knowing how these two mindsets differ can make it easier to build code that stays clean and easy to evolve. And as programming continues to pull ideas from functional, reactive, and declarative styles, the compositional way of thinking will probably stay right at the heart of how we approach object-oriented design. https://d1gesto.blogspot.com/2025/05/the-split-in-oop-compositional-vs.html https://d1gesto.blogspot.com/2025/05/the-split-in-oop-compos...
- elviejo 1y agoIn Eiffel we have multiple inheritance. It's such a powerful tool. And a natural way to model the world. For example if you think of your typical OOP book You have Vehicles with engines * cars that move on roads * planes that move on air * and boats that move on water. But then comes an aqua-plane and it breaks your inheritance tree! But with multiple inheritance is the most natural thing to have a plane that is also a boat and a car. In Eiffel we favor the appropriate tool that better represents the world.
- wild_egg 1y agoThere's some OO design fallacy here but not sure what to call it. The reason the aqua-plane broke the inheritance tree is because the modeling is being done backwards. Objects should be defined by behaviour first and only incidentally contain whatever state is required to support that behaviour. A well designed object is much more similar to a closure than it is to a data structure.
- gsinclair 11mo agoMy programs are not full of engines and cars and planes and boats and aquaplanes. These metaphors used to teach OOP back in the day seemed appealing but never proved their worth in actual software design and implementation.
- DalekBaldwin 1y ago> That points to a deficiency in the “composition over inheritance” aphorism: those aren’t the only two games in town. If you have procedures as first-class types (like blocks in Smalltalk, or lambdas in many languages), then you might prefer those over composition or inheritance. First-class procedures/functions are a form of composition. Requiring a function type behaves like requiring an interface/class type with only one method. (In languages like F#, `Func<_,_>` is literally defined internally as an abstract class with one method, `Invoke`, although there are other mechanisms to auto-inline lambdas when enough static information is available to do so.) In either case, you can place it into a field of an object or data structure, or pass it directly as an argument to a function/method.