0

Liskov Substitution Principle requires that

  1. Preconditions cannot be strengthened in a subtype.
  2. Postconditions cannot be weakened in a subtype.
  3. Invariants of the supertype must be preserved in a subtype.
  4. History constraint (the "history rule"). Objects are regarded as being modifiable only through their methods (encapsulation). Since subtypes may introduce methods that are not present in the supertype, the introduction of these methods may allow state changes in the subtype that are not permissible in the supertype. The history constraint prohibits this.

Can anybody please post an example violating each of these points and another example solving those?

shA.t
  • 15,232
  • 5
  • 47
  • 95
Sam
  • 19
  • 2
  • 1
    Have you check answers to [this question](http://stackoverflow.com/q/56860/4519059) or [this question](http://stackoverflow.com/q/4428725/4519059)? ;). – shA.t Jul 01 '15 at 07:40
  • 1
    [An example using Vehicles is also provided](http://stackoverflow.com/a/20861211/4519059) ;). – shA.t Jul 01 '15 at 07:49
  • I checked the Vehicles example.I think 1st and third conditions are explained quite well in it .But 2nd and 4th are still not clear from the above examples. – Sam Jul 01 '15 at 08:03
  • @shA.t - I personally felt that Rectangle and Square example is quite lame because it does show the problem but not a solution. – Sam Jul 01 '15 at 11:32
  • @Sam Wikipedia [lists a couple of possible solutions to the Rectangle-Square Problem](https://en.wikipedia.org/wiki/Circle-ellipse_problem#Possible_solutions). In practice, removing the inheritance relationship (neither Rectangle is-a Square nor Square is-a Rectangle) works well, as do immutable objects: once created, the properties of these objects cannot be modified. Removing inheritance corresponds to allowing separate invariants for each class, whereas immutability is the most draconic mechanism for enforcing invariants. With immutability, Square is-a Rectangle is allowed. – amon Jul 01 '15 at 15:08

2 Answers2

1

Do you know the ICollection interface? Imagine you are writing a method that gets ICollection and manipulate it by using its Add method or better yet its Clear method If someone passes an ReadOnlyCollection (that implements ICollection) you'll get an exception for using Add. Now you would never expect that since the interface defines that is ok therefore the ReadOnlyCollection violated LSP.

danfromisrael
  • 2,733
  • 2
  • 25
  • 36
1

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.

fhcimolin
  • 565
  • 1
  • 5
  • 22