1. 44
  1.  

  2. 12

    This post was really eye-opening. I was expecting another “it leads to coupling n stuff” but this gave me a way to think about what’s been bothering me. Thanks for that.

    Historically, I don’t think inheritance was “meant” to be that way. If you look at the method wars period (late 80s, early 90s), people were drawing all sorts of relations between classes: refines, implements, abstracts, generalizes, serves, etc. Inherits was the only one that became language construct, at least for a long time. I wonder if that was a major mistake, since it restricted “powerful” relationships to whatever the language designers knew about.

    If that’s the case, I’d want to explore OOP more in dynlangs.

    1. 2

      If that’s the case, I’d want to explore OOP more in dynlangs.

      I’ve found Lua handy for exploring object systems – instead of having one built in, it presents a carefully chosen set of metatable hooks (including some similar to __getitem__ in python and method_missing in ruby). These can be used to construct several kinds of object systems.

    2. 8

      I was skeptical after reading the title, but the article was a positive surprise.

      I beleive that the Liskov SP is the best way to guide our modeling in most mainstream OOP languages. This does not rule out ontological modelling completely, and often this is enough to have a useful model of the domain.

      The latter pargaphs about Traits and “private subclassing” are interesting, and I agree on most of those points. I think the traits based subclassing will become more prevalent in the future. Even in C# with which I work most this is the path often taken via the extension method trick.

      On the other hand I cannot see how could “private subclassing” and non-inheritable operation help in modeling. (on C# this is kind-of possible with explicit interface implementations. Pretty useless most of the time). This would not be visible due to breaking the LSP, thus explicit knowledge of the type would be needed by the user of the contract, which already lets us know the type specific methods.

      1. 6

        I wonder how much of this is about issues in domain modeling. In the example of a square and a rectangle, why do you have a set method? Geometrical objects don’t have setters. The fact that you want that operation tells me that you’re not really interested in geometrical objects, you’re interested in a different type of thing.

        Suppose you’re working with a GUI toolkit, or maybe a geometric puzzle game. You’re not using squares and rectangles there, you’re using SquarePanel and RectangularPanel or whatnot. That avoids the confusion. You can have a resizable panel that temporarily happens to be square, but it’s not a SquarePanel, because it obeys different rules.

        I am tempted to summarize my thinking with “good ontological modeling obeys the LSP”.

        1. 2

          consider a taxonomy of graphs then; like other collections read operations are covariant (if I can read a Graph then I can read a DirectedGraph) and writes are contravariant (if I can add an edge to a DirectedGraph then I can add an edge to a Graph).

        2. 5

          Has anybody here got any experience with/knowledge of BETA-style inheritance, where method dispatch starts at the base class and uses inner to delegate downwards, instead starting at the subclass and using self to call up? I read about it in one article, The Impoliteness of Overriding Methods, but haven’t encountered it elsewhere. Example code (calling ScaryMonster.render() will start at the base class, which sets renderer.setTransform() before calling inner(renderer) to expose a point that subclasses can hook into/override.

          class GameObject {
              float x, y;
          
              void render(Renderer renderer) {
                  renderer.setTransform(x, y);
                  inner(renderer);
              }
          }
          
          class ScaryMonster extends GameObject {
              void render(Renderer renderer) {
                  renderer.drawImage(Images.SCARY_MONSTER);
              }
          }
          

          I’m sorry to just dump this info instead of connecting it to one of the mentioned types of inheritance – I don’t have enough experience with inheritence to quickly work out the connection, and I don’t have enough time right now to slowly work out the connection. I hope it can be of interest anyway.

          1. 4

            Geometrically, a square is a specialisation of a rectangle: every square is a rectangle, not every rectangle is a square. For all s in Squares, s is a Rectangle and width of s is equal to height of s. As a type, this relationship is reversed: you can use a rectangle everywhere you can use a square (by having a rectangle with the same width and height), but you cannot use a square everywhere you can use a rectangle (for example, you can’t give it a different width and height).

            I think there is a reasoning mistake in this paragraph. The definition of (square specializes rectangle) in the first part, which I believe is correct, is “every square is a rectangle”. But then the relation is said to be “reversed” because “you can use a rectangle everywhere you can use a square”. But while this may be correct for some rectangles (those that are square), it is not true that every rectangle can be used where a square was expected. (For example, code that would assume that the area of the argument is width*width would be wrong.) So the idea that “the relationship is reversed” is wrong.

            To make sense the paragraph should be precise about what it means about “use a X where you use a Y”.

            1. 6

              A trivial counterexample to this is inheritance for actors in games, e.g., “EliteMook” extends “Mook”, adding smarter target selection or something.

              1. 7

                Games these days will often drop inheritance in favour of using an entity component system or data oriented approach. This is much more cache friendly than the more naive taxonomic inheritance hierarchy, especially when you have thousands or tens of thousands of entities you need to process.

                1. 2

                  Quite right, at scale.

                  For small games, or for games that don’t have a gigantic update loop, a taxonomic approach you’d see from OOP works fine.

                  My example wasn’t the end-all-be-all, it was just a case where inheritance works pretty nicely. No approach, of course, is going to work forever.

                2. 5

                  I found it interesting that Eiffel is dropped into the article but 6 additional variants on inheritance are described by Bertrand Meyer:

                  https://archive.eiffel.com/doc/manuals/technology/oosc/inheritance-design/section_05.html#HDR8

                  1. 7

                    Because Eiffel imposes that preconditions are contravariant and preconditions are covariant with inheritance, it does impose “subclass means subtype” out of all of the possibilities.

                    1. 6

                      The ironic thing is that while Eiffel preconditions are contravariant, the argument types are covariant. That leads to type errors at runtime and an extremely ugly hack in the Eiffel language to get around it.

                      1. 1

                        I’ve never programmed in Eiffel so I have no insight into the language specifically. I only meant to point out that the author’s ontology of inheritance is not comprehensive.

                        1. 2

                          think of it as a minimal reproduction case for the bug.

                    2. 4

                      Even that counter-example breaks- what happens when the behavior that makes an EliteMook different than a regular Mook also needs to be incorporated into bosses- we have an EliteBoss now. Which sort of touches upon what the article is talking about, because really we’re talking about different kinds of inheritance.

                      1. 4

                        the existence of a counterexample is merely lucky happenstance, when the thesis is “most of the time this situation is not true”. Particularly the ontology of a computer game (or indeed any game; D&D went for decades before multiple inheritance was added) can be more simplistic than one for a business process.

                        1. 4

                          Perhaps a more accurate title would’ve been in order “Why inheritance often does not make sense.”

                      2. 3

                        Debatable. Used (implementation per the article) inheritance, including multiple, on several occasions. It mapped to problem domains really well.

                        1. 6

                          That’s not inconsistent with what the article is arguing, is it? The argument as I read it is that inheritance in general is an incoherent concept, because it’s trying to use one mechanism (or one inheritance tree) to serve what are really three different kinds of goals, which only sometimes by luck happen to overlap. If you pick just one of the goals (like inheritance for the purposes of code reuse), then things get coherent again.

                        2. 3

                          I disagree with the article. Behavioral Subtyping (LSP) is the only one that keeps you out of trouble.

                          1. 6

                            I don’t think that is inconsistent with my thesis. You can want LSP and build an inheritance taxonomy that gives you LSP. You will (probably) have to relax domain fidelity and maximal reuse to get LSP.