• snaggen@programming.devOP
    link
    fedilink
    English
    arrow-up
    2
    ·
    1 year ago

    The core problem I see with Inheritance is that the abstractions tend to fall apart and no longer be true. Lets use the Animal example. It is easy, when you have Animal -> Cat and Animal -> Dog. But as soon as we become more specific like Animal -> Mammal -> Cat, Animal -> Fish -> Hammerhead Shark, Animal -> Bird -> Bald eagle, we risk of getting in trouble. Because now for all purposes we assume things about the Fish, Birds and Mammals, like fish is in the sea and mammals are live on land. We know that this is not strictly true, but for everything we need it works. Later we need to handle a dolphin… should that be a fish, or do we need to restructure the whole program. If we treat it like a fish, then we might be even deeper in trouble later if we would need to handle birth. And even if we restructure our program to be correct to handle birth, we might stil forget that some mammals lay eggs like the Platypus, so then things break again if we need to handle that. We tend to see Inheritance as a rigid fact based structure, but the core problem is that it is just a bunch of assumptions we dictate in a very rigid way that is hard to change.

    Composition have no problem with specifying the platypus as a mammal that lays eggs and have a beak.

      • snaggen@programming.devOP
        link
        fedilink
        arrow-up
        2
        ·
        1 year ago

        If you look at Rust for example, then you specify the Traits instead. So, you could define a trait that defines the properties for birth, another to define if the animal have a beak, and another one to define the number and type of legs. The each animal implement these traits, which then properly can define a duck, cat and a platypus.

  • Jim@programming.dev
    link
    fedilink
    English
    arrow-up
    2
    ·
    1 year ago

    Most of us have bad memories of over-complex hierarchies we regret seeing, but this is probably due to the dominance of OOP in recent decades.

    This sentence here is why inheritance gets a bad reputation, rightly or wrongly. Inheritance sounds intuitive when you’re inheriting Vehicle in your Bicycle class, but it falls apart when dealing with more abstract ideas. Thus, it’s not immediately clear when and why you should use inheritance, and it soon becomes a tangled mess.

    Thus, OO programs can easily fall into a trap of organizing code into false hierarchies. And those hierarchies may not make sense from developer to developer who is reading the code.

    I’m not a fan of OO programming, but I do think it can occasionally be a useful tool.

  • dragontamer@lemmy.world
    link
    fedilink
    English
    arrow-up
    2
    ·
    1 year ago

    Inheritance is useful.

    However, “Dog isa Animal” is worse than useless, it actively hampers your code and makes your life worse.

    Useful inheritance patterns are all over the place in GUI / Model View Controller code however. “Button isa Window”, and “FileStream isa Stream”, and “StringStream isa Stream” in C++. If you stick with SOLID principles, inheritance helps your code significantly.

    • snaggen@programming.devOP
      link
      fedilink
      English
      arrow-up
      1
      ·
      1 year ago

      But even there a TcpStream, a FileStream and a StringStream might have quite differen behaviour, since they all abstract very different things. So, even this simple example may fall apart very fast if you need to care about those. I’m not saying that Inheritance is always bad, but it is quite rigid and might cause problems in a large codebase if you suddenly run in to the corner cases where the assumptions the abstraction is based upon is no longer upheld.

      • dragontamer@lemmy.world
        link
        fedilink
        English
        arrow-up
        1
        ·
        1 year ago

        But the fact that TCPStreams isa file-descriptor, Files isa file-descriptor, Pipes isa file-descriptor, and other such “stream-like objects” in the Linux kernel proves that the read/recv and write/send system calls are generic enough to work in a wide variety of circumstances.

        Yeah, they’re all different. But as far as the Linux API goes, they’re all file descriptors under it all and have abstractions that work well for inheritance in practice. In many cases, inheritance doesn’t work. But in many cases, it works. And works well, for decades.

        • nous@programming.dev
          link
          fedilink
          English
          arrow-up
          0
          ·
          1 year ago

          Inheritance here is limiting. Composition creates a far nicer API. Take both go and rust, they use a set of different interfaces/traits that cover, Reader, Writer, Closer, Seeker. Not everything implements all of these and can implement any set of them. For instance, a File implements them all, as it can be read from, be written to, closed and arbitrarily seeked. But a TCP Stream cannot random seek to other locations inside it - you cannot suddenly reset a network connection to the start again, those bytes are lost. So even if they are both file deciphers they do have different behaviours.

          And these can be used for more than just files and network connections - you can write into or read from a vector/list of bytes, even seek randomly into it, but cannot close it as that does not make any sense.

          Most languages cannot have multiple inheritance - so you are stuck with a abstract class with all the functions on it with blank implementations so the unsupported functions become no-ops. But IMO that is just a hack to workaround the lack of being able to split these different types up.

          • dragontamer@lemmy.world
            link
            fedilink
            English
            arrow-up
            0
            ·
            1 year ago

            You’re not describing composition.

            Go Files do not “hasa reader”. You don’t do file.reader.read(), you just do file.read(), that’s inheritance as file has inherited the read() method.

            • colonial@lemmy.world
              link
              fedilink
              English
              arrow-up
              1
              ·
              1 year ago

              You’re confusing polymorphism for inheritance. read is a method on an interface that File implements - it is not inherited from a base class. You can use that File directly, or wherever a Reader interface (or whatever the name is, idk I don’t really do Go) is expected.

            • orangeboats@lemmy.world
              link
              fedilink
              English
              arrow-up
              0
              arrow-down
              1
              ·
              1 year ago

              Composition do not necessitate the creation of a new field like x.reader or x.writer, what are you on?

                • platypus_plumba@lemmy.world
                  link
                  fedilink
                  English
                  arrow-up
                  1
                  ·
                  edit-2
                  1 year ago

                  Man, I honestly have no idea why they are downvoting you. Composition literally means taking common behavior and placing it in external objects behind interfaces using a has-a relationship.

                  No idea why they are denying this. Inheritance vs composition is the same as “is-a” vs “has-a”. In composition re usability isn’t done via inheritance but code encapsulation.

                  Saying that in Go objects can implement many interfaces is the Interface Segregation principle from SOLID. Basically having small interfaces instead of a big interface with methods that may not be implemented by all implementors.

  • zygo_histo_morpheus@programming.dev
    link
    fedilink
    English
    arrow-up
    1
    ·
    1 year ago

    I find that inheritance isn’t that useful when it comes to helping me reason about programs, which is more important to me than avoiding code duplication. The ability to create subclasses can be convenient for adding new code but the same extensibility also means that I have to consider all potential future uses of a class when I’m using one. It’s easy to make assumptions when writing code that uses a class than can then be broken by future subclasses. You can try to mitigate this by writing clear invariants in advance and updating them when you find that you need to make more assumptions, but then this means that you have to give up the flexibility of inheritance.