Reputation: 5047
I am learning about LSP in detail and I do understand why strenghtening preconditions violates the principle (using the example from http://www.ckode.dk/programming/solid-principles-part-3-liskovs-substitution-principle/#contravariance):
public class SuperType
{
public virtual string FormatName(string name)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentException("name cannot be null or empty", "name");
return name;
}
}
//VIOLATING ONE
public class LSPIllegalSubType : SuperType
{
public override string FormatName(string name)
{
if (string.IsNullOrEmpty(name) || name.Length < 4)
throw new ArgumentException("name must be at least 4 characters long", "name");
return name;
}
}
Here I clearly can see that what would be valid for the base class would fail for its derivative. In other words, I would not be able to replace the base class with its derivative without changing the behavior.
Now the following method is considered legal, as it weakens the preconditions:
public class LSPLegalSubType : SuperType
{
public override string FormatName(string name)
{
if (name == null)
throw new ArgumentNullException("name");
return name;
}
}
Quoting the site: this is perfectly legal as any valid arguments to the supertype will also be valid in the subtype.
Well, but what about invalid arguments? If I have a code that calls the SuperType with invalid arguments (e.g. empty name), it fails. If I replace it with a subtype, the same call will not fail because the condition is weaker. So in that sense, I cannot replace the supertype with subtype as it would change the behavior too. I am puzzled.
Upvotes: 1
Views: 753
Reputation: 144206
A method with pre and postconditions is stating that when the precondition is satisfied by the caller, then it guarentees the postconditions will be satisfied on exit. However, the contract does not state what will happen if the precondition is not satisfied - it is still permitted for the method to complete successfully. Therefore subtypes can weaken the precondition, since callers cannot make any assumptions on the behaviour of a method if they fail to satisfy the subtype's precondition.
Upvotes: 0
Reputation: 8665
If you weaken a precondition, the subtype is still compatible with places expecting the supertype. It may not throw an exception where the base class does normally, but that is okay, because throwing less exceptions should not break the consuming code. If the calling code is built around the assumption that exceptions get thrown in certain places, and uses that for primary control flow of the application, the consuming code should probably be rewritten.
Also, I think your second code sample is wrong.
If the preconditions of the base class really must be enforced all the time, a better implementation would be to create a data type that encapsulates those rules, and pass that as a parameter. That way it is not in the hand of subclasses, it is part of the new class's constructor.
Ex:
public class UserName
{
public string Value { get; }
public UserName(string value)
{
if (string.IsNullOrWhitespace(value) || value.Length < 4)
throw new ArgumentNullException(nameof(value));
Value = value;
}
}
public class BaseClass
{
public virtual void Foo(UserName username)
{
//No precondition checks required here
}
}
public class DerivedClass : BaseClass
{
public override void Foo(UserName username)
{
//No precondition checks required here
}
}
Upvotes: 2