The Liskov Substitution principle states that you should write your class inheritance such that swapping subtypes for their base types wont change the behavior of your application by doing so.
However, the virtual keyword literally seems to exist to allow a subtype to behave differently from the base type. Wouldn't most uses of the virtual/override keywords (not including overriding abstract members) be likely to violate Liskov? I feel there may be more nuance to this than I'm understanding. Perhaps this is a case where "The rules exist to be broken sometimes" or perhaps there is a grey area to the "not behave differently" part of the principle.
CodePudding user response:
To quote myself:
Passing an object’s inheritor in place of the base class shouldn’t break any existing functionality in the called method. You should be able to substitute all implementations of a given interface with each other.
Read more of my thoughts on Liskov substitution in C# here: https://docs.microsoft.com/en-us/archive/msdn-magazine/2014/may/csharp-best-practices-dangers-of-violating-solid-principles-in-csharp
The virtual
keyword is not a culprit in and of itself. The is
keyword is more often a culprit for minor Liksov violation, as people use it to check if their active instance is a certain inheritor, and thus ensure that behavior changes based upon inheritance. The new
keyword when used on methods is worse; see the above article.
CodePudding user response:
Unfortunately, "It depends".
The Liskov Substitution principle (LSP), in general is undefined. It's supposed to take the idea of type substitutability (in the type theoretic sense) and extend it into the realm of behaviours. That is, a sub-type's behaviour must "extend" the behaviour of the super-type. The issue is that it does not give a good definition of what those behaviours are and how you can test if you are changing them.
There are many examples of what apparently constitutes a violation of the LSP. Most of those are flawed as they fail to take into the prerequisite that all type substitutions, including all valid covariant and contravariant alterations to the method(s) and type parameters in the interface, are correct. The LSP is an extension to this type checking. Additionally, the ability to use this variance in the signatures is a requirement to correctly implement many behaviours of an interface, therefore satisfying the LSP.
E.g. Take the Clone() method. If you have an abstract class Mammal and a subclass Human, the Human class MUST return a Human when Clone is called. This is because there is expected behaviour of the Clone() method that is codified in the use of the English word "clone". Returning a Mammal violates that expected behaviour.
While the correct implementation of the Clone() method is impossible in C#, the virtual keyword enables a "close enough" approximation, therefore not only is virtual NOT a violation of the LSP, but it's a requirement IF you use base classes.
Lastly, the LSP, like all the principals in SOLID, is more a collection of "code smells" than an actual definable rule. They are there to give you a way of discussing why code isn't good. It does not tell you what the real issue is with the code, nor does it tell you how to correct it or how to prevent it from occurring.
CodePudding user response:
First, LSP doesn't say the subtype can't change the behavior, it says that subtypes can't change the properties assumed by on the superclass.
Quoting Wikipedia:
Subtype Requirement: Let ϕ(x) be a property provable about objects x of type T. Then ϕ(y) should be true for objects y of type S where S is a subtype of T.
That is, the subtype may not change ϕ, with ϕ being any assumption ("provable property") you can make on the supertype.
Second, if your superclass has a virtual
method and the subclass overrides it to have it exit the program, does it violate LSP? Only if the caller couldn't assume that on the superclass' method.
What this means, is that it's easy to write superclasses with virtual methods that make it easy to violate LSP. But it isn't an inherent property of virtual
.
What the language could, perhaps, do better, however, was to force the virtual
override to at least call the superclass' method somewhere within its code.