Live data from Hacker News

Liskov Substitution: The real meaning of inheritance

cekrem.github.io

61–70 of 92 posts

Re: Liskov Substitution: The real meaning of inheritance

#61

Earlier quoted context omitted.

What difference do you see between implementing an interface and inheriting from a class, that makes one good and the other bad? I'm asking beyond the arbitrary distinctions that some languages like Java or C# bake in.

Inheriting from a class creates dispatch problems, and there's instance variables/fields/whatever to deal with. Never mind multiple inheritance, as that gets hairy real fast. With interfaces there is no hierarchy, and all there is is a type and a data, and you either pass them around together, you monomorphize to avoid the need to pass two pointers instead of one, or you have the data point to its type in a generic w…

this! with interfaces, you get all the benefits and none of the negatives

Re: Liskov Substitution: The real meaning of inheritance

#62
post #4

Do yourself a favor and wear yourself off all this SOLID, Uncle Bob, Object Oriented, Clean Code crap. Don't ever use inheritance. Instead of things inheriting from other things, flip the relationship and make things HAVE other things. This is called composition and it has all the positives of inheritance but none of the negatives. Example: imagine you have a school system where there are student users and there are…

Inheritance is nothing more or less than composition + implementing the same interface + saving a bit of boilerplate. When class A inherits from class B, that is equivalent to class A containing an object of class B, and recoding the same interface as class B, and automatically generating method stubs for every method of the interface that call that same method on your B. That is, these two are perfectly equivalent:…

for one, see the reply from the handle cryptonector further up

> Inheriting from a class creates dispatch problems, and there's instance variables/fields/whatever to deal with. Never mind multiple inheritance, as that gets hairy real fast. With interfaces there is no hierarchy, and all there is is a type and a data, and you either pass them around together, you monomorphize to avoid the need to pass two pointers instead of one, or you have the data point to its type in a generic way. With class hierarchies you have to have operations to cast data up and down the hierarchy, meaning trade one dispatch table for another.

I agree with all of this. With interfaces, you get all the benefits, but none of the downsides.

Re: Liskov Substitution: The real meaning of inheritance

#63

SOLID and clean code are not some universal bible that is followed everywhere, I spend a considerable amount of effort reasoning juniors and mid levels out of some of the bad habits they get from following these principles blindly. For example the only reason DI became so popular is that you could not mock static in Java at the time. In FB codebase DI was also used in PHP until they found a way to mock static, after…

> sins caused in name of DRY by juniors

A discussion of clones that can be OK and more: https://cormack.uwaterloo.ca/~migod/papers/2008/emse08-Clone...>

Re: Liskov Substitution: The real meaning of inheritance

#64

Earlier quoted context omitted.

There is no confusion if you understand that Inheritance is just a "mechanism" to express three (and maybe more) different kinds of "policies" and a single class may implement any or all of them in which case it becomes important to disambiguate which methods/functions express which "policies". There is a abstract concept and a syntactical expression of that concept which needs to be clear in one's mind. Again, asser…

I agree that inheritance does too many things and has too many degrees of flexibility. I think other kinds of polymorphism like typeclasses don't have this issue, and are better due to that. Automation is highly preferable to documentation. I think the discussion would benefit from you concretely working through an example. What change(s) are you proposing to how inheritance is done in C++ or Java, and how would they…

I am not sure that you understood what i wrote. Inheritance's flexibility is its very strength that allows you to express different concepts elegantly. Also no amount of Automation/Tooling/etc. can substitute for documentation explaining the intent behind the code.

The main thing i would like to see in C++/Java/whatever is support for "Design-by-Contract" (DbC) similar to that given in Eiffel. There is already a proposal for C++; see this recent HN discussion - https://news.ycombinator.com/item?id=42131473 Basically this is a way to apply Hoare Logic to functions/methods directly in the implementation language itself. Now use this across the types/classes in a inheritance hierarchy and you can enforce the semantics that you want to express.

Regarding a concrete example; I have already pointed you to Bertrand Meyer's OOSC2 and three specific chapters to read; They walk you through proper examples with explanations. Additionally also see his Applying Design By Contract paper (pdf) here - https://se.inf.ethz.ch/~meyer/publications/computer/contract.... If you would like to see complete application code using a C++ OO framework i suggest creating a sample MFC app using Visual Studio C++ wizard and just looking at the generated code without adding anything of your own. It uses a Document/View architecture (a variation of MVC) where your app specific classes derive from MFC framework given classes. The framework invokes your derived methods (i.e. downcall) which can as needed call back to base's method (i.e. upcall). There is a strong coupling between the framework classes and your app specific ones by design. You can see how different usages of inheritance are implemented to give a powerful app framework; see documentation starting here - https://learn.microsoft.com/en-us/cpp/mfc/document-view-arch...

Re: Liskov Substitution: The real meaning of inheritance

#66

Earlier quoted context omitted.

I agree that inheritance does too many things and has too many degrees of flexibility. I think other kinds of polymorphism like typeclasses don't have this issue, and are better due to that. Automation is highly preferable to documentation. I think the discussion would benefit from you concretely working through an example. What change(s) are you proposing to how inheritance is done in C++ or Java, and how would they…

I am not sure that you understood what i wrote. Inheritance's flexibility is its very strength that allows you to express different concepts elegantly. Also no amount of Automation/Tooling/etc. can substitute for documentation explaining the intent behind the code. The main thing i would like to see in C++/Java/whatever is support for "Design-by-Contract" (DbC) similar to that given in Eiffel. There is already a prop…

Sorry I don't think your response really gets to the point. I'm aware of various techniques like contracts, but you're speaking in generalities rather than specifics. So yes, I haven't quite understood what you meant.

This is a common frustration I have with OOP discourse, it tends to be really up-in-the-air and not grounded in concrete specifics. (The article you linked also has this issue.) Meanwhile, users suffer in ways that just don't happen with typeclass-based polymorphism, and none of this discourse is required in my world. So why should I not recommend everyone use typeclass-based polymorphism?

> I am not sure that you understood what i wrote. Inheritance's flexibility is its very strength

No, being too flexible is a weakness, not a strength. At scale, rigorous discipline enforced by tooling is required.

> Also no amount of Automation/Tooling/etc. can substitute for documentation explaining the intent behind the code.

Yes, of course documentation is required. What I'm saying is that if it can be automated, it should be, and that relying on documentation alone is foolish.

In particular, invariants like "no downcalls" or "no upcalls" should 100% be enforced by automation. Documentation is not enough at scale and under pressure.

> i suggest creating a sample MFC app using Visual Studio C++ wizard

I'd rather not?

> The framework invokes your derived methods (i.e. downcall) which can as needed call back to base's method (i.e. upcall).

This sounds really bad to me at scale and under pressure.

Re: Liskov Substitution: The real meaning of inheritance

#67
post #36

> class Square : Rectangle() { ... What if instead of Rectangle class we would have ReadonlyRectangle and Rectangle? Square could then inherit from ReadonlyRectangle, so code expecting only to read some properties and not write them could accept Square objects as ReadonlyRectangle. Alternatively if we really want to have only Square and Rectangle classes, there could be some language feature that whenever you want to…

What is "ReadonlyRectangle"? Is it just an interface that only exposes read-only methods; or is it an explicit promise that the rectangle is immutable?

Perhaps we could go with even more classes. "Rectangle" and "Square" for the read-only methods, without any implications about mutability. "MutableRectangle" and "MutableSquare" for mutable implementations; "ImmutableRectangle" and "ImmutableSquare" for immutable implementations.

- "Rectangle" has methods "getWidth" and "getHeight".

- "Square" has a method "getSide".

- "ImmutableRectangle" implements "Rectangle".

- "ImmutableSquare" implements "Rectangle" and "ImmutableRectangle" and "Square".

- "MutableRectangle" implements "Rectangle"; has extra methods "setWidth" and "setHeight".

- "MutableSquare" implements "Rectangle" and "Square"; has an extra method "setSide".

...or you could just give up, and declare two classes "Square" and "Rectangle" (mutable) that either have nothing in common, or they just both extend some "Shape" class that can paint them and calculate the area.

Re: Liskov Substitution: The real meaning of inheritance

#68
post #52

Liskov substitution will not save you. One of the worst cases of inheritance I've ever seen was in a hierarchy that was a perfect Liskov fit -- an even better fit than traditional examples like "a JSON parser is a parser". See https://news.ycombinator.com/item?id=42512629 . The fundamental problem with inheritance, and one not shared by any other kind of polymorphism, is that you can make both upcalls and downcalls w…

I've never seen a case where inheritance was superior to composition with a shared interface. Worst case with composition, it just returns the injected class's method directly. The beauty is that this really shines when you apply the liskov substitution principle.

I think Python's pattern using inheritance for mixins is probably a good candidate. But Python does have a culture of "inheritance is only for sharing code user beware if you try to use it for other things." Python's ABC classes for collections is also a good use of inheritance. Inherit from MutableMapping, implement the required methods, boom you get all the other mapping methods for free.

Pydantic / dataclass inheritance is elegant for building up different collections of fields. That being said it does use codegen / metaclass hackery to do it.

Re: Liskov Substitution: The real meaning of inheritance

#69
post #4

Do yourself a favor and wear yourself off all this SOLID, Uncle Bob, Object Oriented, Clean Code crap. Don't ever use inheritance. Instead of things inheriting from other things, flip the relationship and make things HAVE other things. This is called composition and it has all the positives of inheritance but none of the negatives. Example: imagine you have a school system where there are student users and there are…

Inheritance is nothing more or less than composition + implementing the same interface + saving a bit of boilerplate. When class A inherits from class B, that is equivalent to class A containing an object of class B, and recoding the same interface as class B, and automatically generating method stubs for every method of the interface that call that same method on your B. That is, these two are perfectly equivalent:…

This is not quite correct, at least not in most commonly-used languages. The difference comes when a "superclass" method calls self.whatever(), and the whatever() is implemented in both the "superclass" and the "subclass". In the implementation-inheritance-based version, self.whatever() will call the subclass method. In the composition-based version, self.whatever() will call the superclass method. The implementation-inheritance-based version is sometimes called "open recursion". This is why you need implementation inheritance for the GoF Template pattern. See for instance this paper:

https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&d...

Post reply on HN