Shouldn't that be subtype? Or am I misremembering this?
Shouldn't that be subtype? Or am I misremembering this?
Prefer composition over inheritance for reuse. And prefer interfaces over base classes (abstract or concrete) for polymorphic behavior.
There aren't many situations where Liskov is even relevant with those principles in mind. I have to inherit from a concrete type from time to time when when a third-party API requires it. And, inevitably, my design becomes more convoluted than necessary because of that.
In order to make it easier to follow LSP, it's wise to minimize the amount of concrete implementation in your base types. The more you can minimize it, the less likely you are to encounter a situation where a base type implements behavior you don't want. That in turn reduces the spots where you might even want to violate LSP.
The logical end to this is purely abstract base classes which contain no implementation. These types are so easy to reuse that some languages have even enshrined them as their own special language construct (typically called "interfaces"), and given them special privileges that types which allow implementation don't get to enjoy, such as multiple inheritance. The reason they get these special privileges is that it's relatively easy for them to enjoy those features without violating LSP, whereas it's inherently tricky for types that include concrete implementation to do the same.
It's also worth noting that ABCs are especially problematic when physical representation of data is concerned. Inheritance in C++ affects memory layout, for instance. And adding new serializeable members to a base class in any language is a reverse-incompatible change.
As a contrasting example, Java and .NET interfaces aren't allowed to contain fields, only method and property signatures. Which really means only method signatures, since Java and .NET properties are just methods + syntactic sugar. Both languages also have ABCs, but they are distinct language constructs that don't get to enjoy the same privileges as interfaces.
That may mean that C++ abstract base classes don't quite hit it on the LSP front. It's also true that C++ is a language that predates LSP's formulation by over a decade, so we can't really expect it to be a language that's designed to take LSP into account, anyway.
Long story short, though, just because LSP is baked into your programming language doesn't mean it's moot. It just means you're working in a language that's well-designed on that front.
The interfaces that complicate my designs were written by well-meaning engineers that thought ABCs are best practice if the LSP is followed. That's not the case anymore and I'd rather we start teaching from a more sensible default.
LSP as most people seem to currently conceive it is coloured by their experience with languages like C++, where inheritance and polymorphism are welded to one another. Same goes for the likes of Java, albeit to a lesser extent. In the case of languages like that, LSP also needs to deal with the subclass/superclass relationship, and that has over time overshadowed its most important aspect: substitutability of implementations.
Technically there many ways to have polymorphism in C++ without inheritance and virtual methods (closures, templates, tagged unions, etc.). In practice, students learn about polymorphism from OOP textbooks and conflate polymorphism and inheritance.
The work on 'concepts' for (maybe) C++20 fits more nicely with subtyping and LSP, it sounds like.
In practice, this doesn't quite hold though since the principle method by which subtyping is introduced is subclassing. This unification is the source of many woes.
To see an example where this doesn't hold you can take a look at OCaml. It has an object system which separates subtyping and subclassing and helps you to take note of what subclassing is actually about: reusing creation code. It ends up being very similar to normal function re-use at the end of the day and is very nice.
It's just not subtyping.
The way this is worded, it sounds like you would encourage violation of the principle. From the rest of the thread, it seems like you just encourage work where it does not apply. I think a lot of respondents are reacting to the former - while the latter is much less controversial (although note tel's commentary on distinction between type and class).
From Wikipedia[0]:
> Liskov's notion of a behavioral subtype defines a notion of substitutability for mutable objects; that is, if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program (e.g. correctness).
0: https://en.wikipedia.org/wiki/Liskov_substitution_principle
Assuming sub/super are referring to inheritance (sorry, I'm a bad), a sub contains all of a super and more. Passing through an object with more shouldn't break anything. But a super might lack things the sub has, thereby breaking assumptions about what will be present.
I feel like the principle would be broken by the objects/inheritance being badly used rather than the calling code though?