Live data from Hacker News

Liskov Substitution: The real meaning of inheritance

cekrem.github.io

41–50 of 92 posts

Re: Liskov Substitution: The real meaning of inheritance

#42
post #32

From the article: Again, kudos to Uncle Bob for reminding me about the importance of good software architecture in his classic Clean Architecture! That book is my primary inspiration for this series. Without clean architecture, we’ll all be building firmware (my paraphrased summary). What does clean architecture have to do with building firmware or not? Plenty of programmers make a living building firmware. Just beca…

I’m in the middle of reading Clean Architecture right now. The square/rectangle example is directly from the book. The firmware statement is an argument made (differently) in the book that software is called soft because it’s easy to change. Firmware is harder to change because of its tight coupling and dependencies (to the hardware). Software that is hard to change due to tight coupling and dependencies could almost…

You shouldn’t believe what you read, especially from the book clean code. The principles are somewhat okay, the examples are terrible

Re: Liskov Substitution: The real meaning of inheritance

#44

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…

> No one should ever use inheritance in any long-term production use case without some way of enforcing strict discipline, ensuring that calls can only go one way -- up or down, but not both. I don't know to what extent tooling to enforce this discipline exists.

Disagree with your first part. Inheritance used to express Subtyping is different from that used for Code-reuse and yet again different from that used for implementing Framework skeleton structure. You have to disambiguate them carefully when using it. See the article linked to by user "Fannon" here - https://news.ycombinator.com/item?id=42789466

As for tooling, you have to enforce the contract using pre/post/inv clauses following Meyer's DbC and also explicit documentation.

Re: Liskov Substitution: The real meaning of inheritance

#45
post #39

Here to recommend this article, really helped me to understand inheritance better. Liskov Substitution is just one aspect / type of it and may conflict with others. https://www.sicpers.info/2018/03/why-inheritance-never-made-...

Very Good; Gives the "Correct" overview of different usages (i.e. policy) of Inheritance (i.e. mechanism).

Quote: Inheritance was never a problem: trying to use the same tree for three different concepts was the problem.

Re: Liskov Substitution: The real meaning of inheritance

#46

Earlier quoted context omitted.

Just because you see OO languages starting to favor composition over inheritance does not mean inheritance has no place, and indeed, interfaces as a form of composition have existed in many bog-standard OO languages for decades. Your example dosn't compute, at least in most languages, because derived objects would not have the same shape as one another, only the shape of the base class. I.e. functions expecting a Use…

Inheritance has no place in production codebases—unless there is strict discipline, enforced by tooling, ensuring calls only go in one direction. This Liskov stuff has zero bearing.

You are conflating two completely separate things.

The Liskov principle basically defines when you want inheritance versus when you don't. If your classes don't respect the Liskov principle, then they must not use inheritance.

The problems from your story relate to the implementation of some classes that really needed inheritance. The spaghetti you allude to was not caused by inheritance itself, if was caused by people creating spaghetti. The fact that child classes were calling parent class methods and then the parent class would call child class methods and so on is symptomatic of code that does too much, and of people taking code reuse to the extreme.

I've seen the same things happen with procedural code - a bunch of functions calling each other with all sorts of control parameters, and people adding just one more bool and one more if, because an existing function already does most of what I want, it just needs to do it slightly different at the seventh step. And after a few rounds of this, you end up with a function that can technically do 32 different things to the same data, depending on the combination of flags you pass in. And everyone deeply suspects that only 7 of those things are really used, but they're still afraid to break any of the other 25 possible cases with a change.

People piggybacking on existing code and changing it just a little bit is the root of most spaghetti. Whether that happens through flags or unprincipled inheritance or a mass of callbacks, it will always happen if enough people are trying to do this and enough time passes.

Re: Liskov Substitution: The real meaning of inheritance

#47
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…

I think what you mean is that if a Square that is also a Rectangle can't be made to be non-square, then inheritance works. Which, fair enough, but I think there's still other good reasons that inheritance is a bad approach. Interfaces (and traits) are still way better.

Re: Liskov Substitution: The real meaning of inheritance

#48

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…

Can constructors and destructors still go counter to this idea if needed?

Re: Liskov Substitution: The real meaning of inheritance

#49
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…

Never using inheritance is pushing the dogma to the other extreme imo. The whole prefer composition over inheritance is meant to help people avoid the typical OO over use of inheritance. It doesn't mean Is-A relation doesn't/shouldn't exist. It just means when there is data, prefer using composition to give access to that data. There will be times when you want to represent Is-A relationship - especially when you wan…

If you have a sufficiently statically typed language then the is-a concern goes away -- certainly in the example you gave, since the compiler/linker knows to look for a `GetNotifier()` that returns a `Notifier`. Now, you might still want to know whether the notifier you got satisfies other traits than just those of `Notifier`, but you do that by using a type that has those traits rather than `Notifier`, and now you have little need for `instanceof` or similar operators. (You still might want to know what kind of thing you got because you might care about semantics that are not expressed in the interface. For example you might care to know whether a logger is "fast" as in local or "slow" as in remote, as that might cause you to log less verbosely to avoid drops or blocking or whatever. But the need for this `instanceof` goes down dramatically if you have interfaces and traits.)

Re: Liskov Substitution: The real meaning of inheritance

#50
post #21

Earlier quoted context omitted.

I see no need for inheritance there, that can and should be done using interfaces eg in the contrived example I gave, any of all three of the User, Student and Employee can implement the interface (and if needed Student could simply delegate to its internal User while Employee could "override" by providing its own, different implementation)

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 way. With class hierarchies you have to have operations to cast data up and down the hierarchy, meaning trade one dispatch table for another.
Post reply on HN