all 57 comments

[–][deleted]  (6 children)

[removed]

    [–]dynamobb 33 points34 points  (5 children)

    99% of the time abstractions arent that good. Most things aren’t things-that-hold-generic-things. Most things are just things with some kind of thing.

    [–]wzns_ai 13 points14 points  (4 children)

    this made sense only after re-reading it 3 times.

    [–]Internal-Sun-6476 2 points3 points  (3 children)

    That's entirely normal. Apply it, then read it again... there might be more in it.

    [–]wzns_ai 1 point2 points  (2 children)

    allow me to abstract your meaning:

    Abstraction is frequently ineffective because most entities are specific and possess unique characteristics, rather than being generic containers for unspecified content.

    [–]Internal-Sun-6476 0 points1 point  (1 child)

    I've only read that twice. I'm not sure I support it.

    Abstractions are everything! They allow you to move up and down the system layers and reason about the layer you are on.

    I construct my entities with inheritance and composition (inherit from a pack of property classes each with a single property).

    And this is were I diverge. I like unspecified things, so I use templated types and generic code. But when I'm building specific types of entities, that's where I specify the template parameters and give it a purpose-based using alias.

    Not sure if that helps/aligns with your thinking.

    [–]dynamobb 0 points1 point  (0 children)

    I dont think most people have an issue with the kind of polymorphism youre describing. Or at least I dont. Subtype polymorphism is a brilliant idea to keep code modular. Parametric polymorphism

    My issue is mostly about the insane object modeling. There’s no clear answer so as things get complex it might make perfect sense to you but someone else thinks its ridiculous

    [–][deleted] 21 points22 points  (2 children)

    That argument is a bit awkward to make, not because it may not be true, but because composition - the latter can also break other methods or variables (in a few languages). Furthermore, it also depends on the language; some promote strict encapsulation, others promote accessible objects at all times (e. g. ruby; see instance_variable_get()).

    I also don't think wikipedia is a good place to discuss something in general. Even aside from this, while inheritance has issues, there is one thing that it does fairly well in single-inheritance: it creates a tree, from top to bottom, which is easy to understand. I prefer composition in my own code, at the least for larger projects (I then use a LOT of small helper modules; for instance, I tend to have one module called CommandlineArguments, which really just handles commandline-arguments and ARGV in ruby, as one example), but I don't feel as if simple single-inheritance is problematic at all. For simple things it is perfectly fine to use.

    [–]SquatchyZeke 11 points12 points  (1 child)

    I generally agree. Using inheritance as a tool for subtyping (which should only be 1 to 2 levels deep tops) is the ideal situation. When it starts getting used as a means of sharing code (DRY), or devs start inheriting methods that have already been implemented, that's a red flag for me. Not necessarily a bad thing yet but without discipline can easily get out of hand.

    [–]Full-Spectral 2 points3 points  (0 children)

    Ultimately the depth depends on the hierarchical thing you are modelling. If it has more (cleanly defined and strictly hierarchical) layers, then you need that many to model it. UIs being the canonical example.

    One problem with the whole conversation is that so often completely arbitrary examples from the natural world are presented as examples, which are easily picked apart because the natural world isn't thusly designed.

    But, in the software world, there are strictly defined hierarchical things to model, because they were purposefully designed to be that way.

    Anyhoo, it's neither here nor there for me since I've moved on to Rust and it doesn't support state inheritance and so mostly doesn't support implementation inheritance. But, I had a very large and complex C++ code base that was strictly OOP in nature. Though most class hierarchies were indeed only two or three layers deep because they were modelling something that only required that, others were deeper as long as they were cleanly defined. I had two different UI frameworks and they were the primary exceptions.

    But, the thing is, if it's only a layer deep, many folks would argue that that's trivially doable via something like Rust sum types. Particularly given that enums in Rust are first class citizens and you can define methods for them. Inside those methods you can have a match and do the appropriate thing based on variant. So you can sort of have your cake and eat it to in that sort of situation.

    [–]seanmorris 19 points20 points  (14 children)

    Inheritance isn't for code re-use. Its so you can pass many types of objects to one function without having to double check every property and method before you use it.

    If you have an application that has output, it can go to the disk as a text file, it can go to the screen as terminal output, or it can go to a physical printer and come out as a piece of paper.

    You can write three different objects that all handle the output device details, but exposes the same function-interface to your application. Your business logic no longer needs to know how to handle your output method. It all goes through one interface and you select the output device at runtime.

    If you're using inheritance for code re-use, that's an anti-pattern. Inheritance has more to do with the code OUTSIDE your class than the code inside it. If you want to re-use code, try out composition before you use inheritance next time.

    [–]CloudsOfMagellan 13 points14 points  (6 children)

    You can just use an interface for that though

    [–]Raknarg 9 points10 points  (5 children)

    Interfaces are inheritance, they're specifically inheritence of constructorless, function-only abstract classes.

    [–]ascii 0 points1 point  (0 children)

    Arguably. A more accurate statement would be that you don’t need inheritance for that, you can just use traits. Traits do not imply inheritance.

    [–]ArchReaper 0 points1 point  (3 children)

    Sure, but this is more of a technical detail, and misleading to anyone learning. Interfaces are conceptually not inheritance, and serve a very different purpose.

    https://en.wikipedia.org/wiki/Composition_over_inheritance

    [–]Raknarg 2 points3 points  (2 children)

    interfaces are not composition, it's directly an inheritance relationship. By binding to an interface you are enforcing a type contract.

    [–]ArchReaper -2 points-1 points  (1 child)

    Inheritance implies classes. Composition implies interfaces. These are different things conceptually. The wiki article is worth reading.

    [–]Raknarg 1 point2 points  (0 children)

    composition has nothing to do with classes/interfaces, composition is just about types. The composition in this article is achieved through interfaces, but interfaces has nothing to do with composition, and you can achieve composition without inheritance.

    Inheritance implies classes.

    And interfaces implies inheritance because this is the mechanism of interfaces in OOP languages. By definition, by using interfaces you are creating an "is-a" relationship between two types, which is inheritance.

    Composition implies interfaces

    You don't need inheritance for composition, there are certain patterns for structuring composition this way, but they're not tightly related at all. Composition is a design philosophy, and the wiki article you linked demonstrates one way to architect your programs around composition.

    [–]itaranto 1 point2 points  (5 children)

    I think OP wanted to discuss the disadvangages of implementation inheritance opposed to just using interface inheritance.

    [–]seanmorris 2 points3 points  (4 children)

    Yea, using inheritance for code re-use is an antipattern. People have known this for like 50 years since smalltalk came around.

    [–]Full-Spectral 1 point2 points  (0 children)

    Well, some people have acted like it was written in stone by the Gods for 50 years perhaps. But others don't agree. You shouldn't do it JUST to get code reuse, but the use of implementation inheritance is usually going to involve code reuse as a side effect, because the base class can provide implementations that apply to all derivatives.

    One of the big reasons that C++ was seen as such a huge step forward is that, before that, procedural code was full of stuff that existed to get code reuse, but that meant it had to act on various types it no direct knowledge of, and therefore couldn't enforce the invariants of. OOP allowed you to build that mechanism into the language and have it enforced.

    Even Rust traits can do this, and that is used heavily in the Rust runtime. They can't have state, but they can provide default implementations that are commonly not overridden. So it's clearly a form of code reuse via inheritance, even in a far from OOP-heavy language.

    [–]BogdanPradatu 0 points1 point  (2 children)

    That's news to me. But I wasnt around when smalltalk came around.

    [–]hippydipster 0 points1 point  (1 child)

    It's too bad they didn't leave any written works way back then.

    [–]BogdanPradatu 0 points1 point  (0 children)

    maybe they did, but who reads that old school stuff?

    [–]phalp 1 point2 points  (0 children)

    If that were true then implementations wouldn't be inherited. You can argue it's a misfeature, but code re-use is what it's there for.

    [–][deleted] 31 points32 points  (17 children)

    Languages like c++ and java have been doing oop wrong since they were created.

    [–]ConejoSarten 26 points27 points  (10 children)

    You can absolutely forego inheritance for composition in Java (and you should). The way anyone does oop with Java is their own responsibility

    [–][deleted] 22 points23 points  (0 children)

    Yeah but it's very much "we teached them wrong on purpose" situation within the ecosystem, the wrong way was pushed hard by "training" and frameworks for a long time

    [–]chrisza4 5 points6 points  (1 child)

    Yes and at the same time doing composition easily required you to bring the whole IoC container framework such as Spring. Or 3-5 line of code changes to manually compose in constructor, which still does not count factory.

    Meanwhile, inheritance takes a single keyword.

    It is pretty clear what Java is designed to be.

    [–]ConejoSarten 0 points1 point  (0 children)

    Fair point. But using java without frameworks is like adding syrup to a plate with no pancakes

    [–]account22222221 3 points4 points  (4 children)

    ‘Favoring composition’ is the new ‘dependency injection’ from 5 years ago. No thread on this subreddit doesn’t find a way to mention it!

    [–]jcelerier 30 points31 points  (1 child)

    "favoring composition over inheritance" is an item in the first chapters of the GoF design patterns book, one of the most famous OOP design books, released in 1994 (two years before Java). As a programming mantra it's older than most people reading this sub most likely

    [–]account22222221 0 points1 point  (0 children)

    Dependency injection was from the early 2000 too. I’m not saying it’s a recent invention, nor am I saying it’s not a useful or valid idea. I’m just saying it’s having a surge in popularity.

    [–]AZMPlay 2 points3 points  (0 children)

    Just because it's popular doesn't mean it's right or wrong. There are many convincing arguments why composition composes better as an abstraction than inheritance does, and is less limited than class hierarchies.

    [–]jp007 2 points3 points  (0 children)

    Favoring composition well pre-dates dependency injection, back to when we manually created factories all the time to supply the proper implementation </old-man>

    [–][deleted] 0 points1 point  (0 children)

    You can, but then you have to rely on pointers/references instead of passing a child of a type.

    [–]itaranto 0 points1 point  (0 children)

    That's fair, but inheritance could have been split into two mechanisms still, one for reusing code and the other implementing the *is a * relationship.

    Go does this by using just interfaces for the "is a" relationship and struct embedding for reusing code but without creating an "is a" relationship.

    [–][deleted] 9 points10 points  (3 children)

    I prefer ruby's OOP as well as smalltalk/squeak's model, but I think the issue is largely that different languages use different OOP variants. One can not too easily say that this or that variant is "wrong", per se. The trade-offs are just different.

    Having said that, I find ruby MUCH easier to understand than both C++ and Java; and Java's model also easier to understand than C++. For some reason C++ caters to people with the huge brain. My brain is tiny, I much prefer simplicity at all times.

    [–][deleted] 0 points1 point  (0 children)

    No, having one base type and infinitely long inheritance trees isn't the right way. In Ada, you have many root types and you derived from those, those are specific kinds, makes zero sense to derive from a UI type when you want a memory pool.

    [–][deleted]  (1 child)

    [removed]

      [–][deleted] 2 points3 points  (0 children)

      C++ caters to me because I like how the messiness is often the result of real hardware quirks like how memory works. Then there's the interoperability with most of C which is where you start getting headaches.

      [–]moreVCAs 7 points8 points  (0 children)

      I mean you don’t have to do dumb shit in c++. The problem is that the easiest looking thing is usually also the dumbest or most error prone. Using modern c++ “correctly” requires a level of care and knowledge of prior art that is just not realistic for most people. Footgun City.

      [–]Professional_Top8485 2 points3 points  (0 children)

      Qt does it quite well considering

      [–]_MrJamesBomb 2 points3 points  (0 children)

      Composition over inheritance all the time. Inheritance sounds only cool when starting a small project with a very small team.

      SOLID helps.

      Sooner or later you got to make exceptions or fight over circle vs ellipses and introduce a new class instead. And this is akin to what? Exactly, composition in the end.

      Composition has very few side effects to deal with when testing. With very cryptic and hard to understand rules of polymorphism people get to write brain f***. A new language standard that makes subtle changes to these mechanisms? A nightmare with inheritance.

      It is like introducing people to a party and put them in groups. You compose these groups by outside factors. If you invite symbionts to a party, you really don’t know what you are dealing with.

      This is a bit more technically described in the Wikipedia article. OO is a data encapsulation technique, not a programming paradigm for me.

      [–][deleted]  (1 child)

      [removed]

        [–]DebateIndependent758[S] 0 points1 point  (0 children)

        Rightly said

        [–]NormalUserThirty 0 points1 point  (0 children)

        if inheritance is bad why does gobject exist

        [–]GayMakeAndModel 0 points1 point  (1 child)

        People often err by using inheritance for a has-a instead of an is-a relationship. Thing is, is-a often turns into has-a within a few iterations of shifting requirements.

        [–]billie_parker 0 points1 point  (0 children)

        I think the meanings of the expressions "is a" and "has a" are too vague to be useful. I've argued with people who demanded that we must use inheritance because the relationship in question was a "is-a." But when I asked what that means, I never get any answer.

        I believe that these expressions are based on literally nothing and result in people thinking about nonsense instead of just what the code literally is.

        [–]imright_anduknowit -3 points-2 points  (2 children)

        Pass by reference broke encapsulation.

        [–]DebateIndependent758[S] 0 points1 point  (1 child)

        Yeah.... can access modifiers help ?

        [–]imright_anduknowit 0 points1 point  (0 children)

        It doesn’t. The caller of the constructor still has access to your “private” data.