7 ms·
Dumb but genuine question from a non-expert programmer: Most of the "OO" world seems to have abandoned inheritance as an anti-pattern in most cases (apart from
by m4r35n357 6y ago
Dumb but genuine question from a non-expert programmer:
Most of the "OO" world seems to have abandoned inheritance as an anti-pattern in most cases (apart from genuine "is a" relationships).
I'm guessing the two case studies are in the list of exemptions from this rule.
So, is this considered a generally good thing to do in c?
- quelsolaar 6y agoThere are some good reasons to use this. A typical thing is to have a descriptor header followed by multiple types of data. The descriptor header says what kind of data is following. You can obviously just use a void pointer in the descriptor that points to a separate data struct, but having them both in the same memory block means fewer allocations, and fewer cache misses, giving better performance.
- megous 6y agoStruct composition is used pretty much everywhere in the Linux kernel. It's a pretty nice pattern, along with container_of() macro.
- vidarh 6y agoIt's not that the OO world has abandoned inheritance, but that inheritance used to be overused in a lot of cases where composition provides the same flexibility. So the typical advice would be to consider composition first, and to limit inheritance to genuine "is a" relationships as you say. But in some domains there are a lot of genuine "is a" relationships, so that does not in any way remove the use of inheritance.
- rabidrat 6y agoThe feature article is "inheritance using composition"; how/why could you consider strict composition first? Isn't the problem almost entirely multiple inheritance, then?
- vidarh 6y agoThe article is using composition for inheritance as an implementation detail. The effect is the same as "full" inheritance in that the "subclass" is fully inheriting the aspects of the "superclass" unless you introduce a vtable to allow overriding effects. In terms of why to avoid inheritance, there are many reasons not tied to multiple inheritance, but the foremost one is to avoid exposing implementation details that may not make sense. One thing to remember is that in OO public APIs "A is a B" really translates to "A's API is a superset of B's". Which is really not what we often think of as "is a" in daily speech. E.g. we might consider a Circle "is a" Ellipse to be a reasonable statement, because in day to day speech "is a" tends to denote a specialization that may be a superset and subset of different aspects of something at the same time, but if these objects are mutable, then allowing you to set all the parameters of an Ellipse for your Circle object will allow you to create a Circle object that is not actually a Circle. So really, the point is to avoid inheritance unless you actually want to inherit and possibly expand on the full API of the class you're inheriting from. If I want a mutable Circle object, I shouldn't be inheriting from a mutable Ellipse - in fact in terms of API it may well make more sense for my Ellipse to inherit the API of a Circle. You'll often find those kinds of inverted relationships in OO. Some types of OO can handle this - e.g. prototype based inheritance would allow you to simple remove methods that makes no sense in the subclass; alternatively you can trigger exceptions etc., but this violates what is very often a central contract of inheritance that users tends to rely on: That you can treat an object as if it is an object of its superclass in most contexts. It's better then to aim for subclassing to strictly superset the API.
- astrobe_ 6y agoI believe this is because of Liskov's substitution principle (LSP), which is the hidden gotcha that awaits around the corner beyond the "manager is-a person" [1] example. The "square is-a rectangle... or maybe not" discussions that pop up from time to time is an illustration of the problem. In practice, when you produce a lot of classes, derive a lot classes (because of the open-close principle), it is easy to blunder. [1] Yes, they have feelings too.
- vidarh 6y agoThe problem tends to be that "is-a" is conveniently short but it confounds several different types of inheritance. Inheritance in most OO languages really means "api is a superset of, and implementation is a specialization (and possibly superset) of" while in natural language "is-a" implies a relationship where some facets might be expanded, while others may be more restricted. This is really partially a weakness in expressiveness of our languages, partially a problem of our frequent insistence on mutability. Often the problem goes away with immutability: If your "square" is immutable and inheriting from a "rectangle" class that allows setting width and height to different values isn't a problem, because the result would be a new object and that result can be a rectangle. But sometimes we also genuinely would be best served by inheriting implementation and public API separately without having to create cumbersome facades etc. - it's just that making an API of a subclass a subset of the API of the superclass violates a lot expectations people tend to have about OO systems, and so few systems outside of prototype based ones seem to allow it without resorting to ugliness like overriding methods to make them throw exceptions etc.
- sergeykish 6y agoThis is function operating on values with slightly relaxed type checks. Default mode in dynamic languages: let list_add = (n, prev, next) => { prev[1] = n n[1] = next n[0] = prev if (next) next[0] = n } foo = [null, null, 3] bar = [null, null, 5] list_add(bar, foo, null) Looks a bit cooler with C structs names as offset. Inheritance would be building container hierarchy on top of that. Could be brittle in any language.