3 ms·
There is no valid use case I've ever seen where inheritance was better than composition (that does not mean a use case does not exist, but evidence is mounting
by lucasyvas 3y ago
There is no valid use case I've ever seen where inheritance was better than composition (that does not mean a use case does not exist, but evidence is mounting against it). I would say it's used because every language made a poor decision by building that in as the way to encapsulate reusable logic. i.e., a mistake.
Some APIs may expose themselves as requiring you extend a base class, and in that case you might as well do what the library author said. But overall? Yes, some things can work well with inheritance (eg. classical UI frameworks), but these are often equally good with a composition based API.
One job I was at I added a lint rule to flag `extends` as an error. You would need to add a comment to justify why you were using inheritance - it went over more favourably than I expected. I think I would suggest this for any new projects using a language that supports inheritance - discourage it with automation, but allow really good reasoning to prevail in review. It is the worse solution at least 99% of the time because it requires you predict a future that is painful to change.
- TheBigSalad 3y agoComposition implies a level of complexity. There may be a near infinite number of permutations of things that can be combined. If in reality there are only a fixed set of variations, inheritance may model your system in a tighter way. The benefit can be simplicity.
- Capricorn2481 3y ago> Composition implies a level of complexity How?
- TheBigSalad 3y agoInheritance is more limited in what you can do and implies a finite set of behavior states. With composition you have a system that can, in theory, have an untestable amount of permutations of behavior strung together.
- Capricorn2481 3y agoNot really? You can use interfaces with composition that keep you from plugging anything into anything. What you're describing sounds more like functional programming while ignoring the names of functions.
- TheBigSalad 2y agoFor a simple example, I might have class A that contains an instance of interface/base type B and another field of interface/type C. If B and C each have 4 implementations that means class A can be composed 4 * 4 = 16 ways. But in practice there may only be matching types of B and C that line up such that there's really only going to be 4 distinct permutations of A. I could model this more easily with inheritance.
- Capricorn2481 2y agoThis has not been a practical problem in my experience. Because composition makes it easier to tell what something does, people are not putting the incorrect implementations of things together. They're essentially just functions with contracts. Very easy to read. With inheritance, you can't know what something does until you know what everything it inherits does. The little method you override could change the entire execution flow of the code. It's extra steps with no benefits.
- Supermancho 3y agoIf I combine A and B to make C, I have more than A and B. Now I have A and B and C and the relationship of A to C and B to C. Composition usually creates complexity. Interfaces (via aspects, aspects, traits, whatever) tend to simplify, when the complexity is quantitatively extensive enough to outweigh the cost of the interface complexity creation + the complexity saved by using the interface. At least, that's how I think of it.
- Capricorn2481 3y agoWhy are you making this distinction between composition and interfaces? Just because you're using interfaces does not mean you aren't doing composition. > Interfaces (via aspects, aspects, traits, whatever) tend to simplify, when the complexity is quantitatively extensive enough to outweigh the cost of the interface complexity creation + the complexity saved by using the interface. That sentence is more complicated than anything that has been done with composition.
- Supermancho 3y agoInheritance is unidirectional composition. I'm combining the parent with the child. Making the relationship simple to create and hard to complicate, is why it endures, afaik.
- davidalayachew 3y agoI can think of one. For Java at least. Imagine that I have a class that has 15 methods -- 14 are a perfect fit as is, but 1 of them needs to be completely overwritten. If I were to do inheritance, I would do `extends` on the class, and then `@Override` the offending method. Problem solved. If I were to do composition, I could use the Delegation Pattern from the Gang of Four, but that would require me to write 14 do-nothing methods just to be able to override the one. In general, composition has more use than inheritance. However, inheritance has a non-zero number of use cases where it is a better choice than composition.
- hnick 3y agoIt really depends on what the code variation is, and it can often be a code smell, but I like just adding a flag to the method for simpler cases of this. No complexity or overhead, easy to reason and debug, and you can retain the current method signature with a default-case call to the new method, so no other refactoring required. It can be inelegant, clunky, and terrible practice if you're doing two totally different things in the method, but I've had a lot of cases recently where it was a nice simple solution that made sense in the context we used it. For me, one time I like that we use simple OO is a 'DeletedMyClass' extending 'MyClass'- it does everything the same, just has a DeletedDateTime property. A few pages will do an 'is' check (we use C#) and render certain things differently, but apart from that, they both get passed around interchangeably. You could argue you could add an IsDeleted flag, but the default case is that most things we deal with are not deleted, and they shouldn't have to even know about that concept. So to me this is a very useful minimal case of OO.
- davidalayachew 2y agoThat's fair. Though, my point stands. Once that number starts to grow, that's when inheritance starts to shine. Again, only in Java as is. And yes, excellent wording with the idea of "Should not even know about the concept". I think that is another good place where inheritance shines.
- mrkeen 3y agoI have just the one use case: to escape out of framework/annotation hell. Say I have a well-designed, self-contained BusinessService class which is perfectly unit-testable. Then someone comes along and sprinkles some @Magic bullshit on it. Now its true behaviour won't be exercised in plain junit - you need custom test runners with class-path scanning and late-binding configuration ("The gorilla holding the banana and the rest of the jungle" in Joe Armstrong speak). If you want to reclaim your unit-testable class, you can divide it into two: SpringBusinessService extends BusinessService. Move all the Spring dependencies out of the latter into the former. Now you get the best of both worlds - clean, unit-testable logic, and a service that Spring can obfuscate with middleware to produce 200-line-long stacktraces after a lengthy startup.