All four items in the question have been thoroughly reviewed in this article.
Preconditions cannot be strengthened in a subtype.
This answer presents "real duck" and "electric duck" example, I suggest you go check it out. I'll use it in this item, for brevity.
It means that subtypes can't get in the way of how the original methods behaved in the base class. In the above mentioned answer's code, both ducks can swim, but the ElectricDuck
will only swim if it's turned on. Therefore, any unit of code that requires that a duck (from the interface IDuck
) swim now won't work, unless explicitly specified that the duck is ElectricDuck
(and then turned on), which needs to be implemented everywhere.
Postconditions cannot be weakened in a subtype.
For this one, we can step back from the duck analogy. Let's take this answer as a base. Assume we have a baseclass that accepts only positive integers. If in a subtype, while extending the method, we remove the condition that the number must be positive, then all units of code that used to take for granted that the number was positive is now under risk of breaking, since now there's no guarantee that the number is positive. Here's a representation of this idea:
public class IndexBaseClass
{
protected int index;
public virtual int Index
{
get
{
//Will return positive integers only
return index < 0 ? 0 : index;
}
set
{
index = value;
}
}
}
public class IndexSubClass : IndexBaseClass
{
public override int Index
{
get
{
//Will pay no mind whether the number is positive or negative
return index;
}
}
}
public class Testing
{
public static int GetIndexOfList(IndexBaseClass indexObject)
{
var list = new List<int>
{
1, 2, 3, 4
};
return list[indexObject.Index];
}
}
If we call GetIndexOfList
passing an IndexSubClass
object, there's no guarantee that the number will be positive, hence potentially breaking the application. Imagine you're already calling this method from all over your code. You'd have to waste your time checking for positive values in all implementations.
Invariants of the supertype must be preserved in a subtype.
A parent class may have some invariants, that is, some conditions that must remain true for as long as the object exists. No subclass should inherit the class and eliminate this invariant, under the risk of all implementations so far breaking down. In the example below, the parent class throws an Exception if it's negative and then set it, but the subclass just plain ignores it, it just sets the invariant.
The following code was taken from here:
public class ShippingStrategy
{
public ShippingStrategy(decimal flatRate)
{
if (flatRate <= decimal.Zero)
throw new ArgumentOutOfRangeException("flatRate", "Flat rate must be positive
and non-zero");
this.flatRate = flatRate;
}
protected decimal flatRate;
}
public class WorldWideShippingStrategy : ShippingStrategy
{
public WorldWideShippingStrategy(decimal flatRate)
: base(flatRate)
{
//The subclass inherits the parent's constructor, but neglects the invariant (the value must be positive)
}
public decimal FlatRate
{
get
{
return flatRate;
}
set
{
flatRate = value;
}
}
}
History constraint (the "history rule").
This one is the same as the last rule. It states that the subtype should not introduce methods that mutate an immutable property in the parent class, such as adding a new Set
method in a subclass to a property that once was only settable through the constructor.
An example:
public class Parent
{
protected int a;
public Parent(int a)
{
this.a = a;
}
}
public class Child : Parent
{
public Child(int a) : base(a)
{
this.a = a;
}
public void SetA(int a)
{
this.a = a;
}
}
Now, a previously immutable property in the parent class is now mutable, thanks to the subclass. That is also a violation of the LSP.