SOLID: Liskov Substitution Principle

Liskov Substitution Principle was introduced by Barbara Liskov in 1987. She described this principle in mathematical terms as below:

Let φ(x) be a property provable about objects x of type T. Then φ(y) should also be true for objects y of type S where S is a subtype of T.

LSP guides how to use inheritance in object-oriented programming. It is about subtyping, and how to correctly derive a type from a base type. Robert Martin explains LSP as below:

Subtypes must be substitutable for their base type.

Here, the type can be interface, class, or abstract class in C#.

Let's simplify it further. A derived class must be correctly substitutable for its base class. When you derived a class from a base class then the derived class should correctly implement all the methods of the base class. It should not remove some methods by throwing NotImplementedException.

Consider the following IMyCollection interface which can be implemented to create any type of collection class.

public interface IMyCollection
{ 
    void Add(int item);
    void Remove(int item);
    int Get(int idex);
}

public class MyReadOnlyCollection : IMyCollection
{
    private IList _collection;

    public MyReadOnlyCollection(IList<int> col)
    {
        _collection = col;
    }
    public void Add(int item)
    {
        throw new NotImplementedException();
    }

    public int Get(int index)
    {
        return _collection[index];
    }

    public void Remove(int item)
    {
        throw new NotImplementedException();
    }
}

The above example violates the Liskov Substitution principle because the MyReadOnlyCollection class implements the IMyCollection interface but it throws NotImplementedException for two methods Add() and Remove() because the MyReadOnlyCollection class is for the read-only collection so you cannot add or remove any item. LSP suggests that the subtype must be substitutable for the base class or base interface. In the above example, we should create another interface for read-only collection without Add() and Remove() methods.

Let's understand what is the meaning of "A derived class should correctly implement methods of a base class".

Consider the following Rectangle class:

public class Rectangle {
    public virtual int Height { get; set; }
    public virtual int Width { get; set; }
}

Mathematically, a square is the same as a rectangle that has four equal sides. We can use inheritance "is-a" relationship here. A square is a rectangle. The Square class can inherit the Rectangle class with equal height and width, as shown below.

class Square : Rectangle
{
    private int _height;
    private int _width;

    public override int Height
    {
        get { return _height; }
        set { 
            _height = value; 
            _width = value;    
        }
    }

    public override int Width
    {
        get { return _width; }
        set
        {
            _width = value;
            _height = value;
        }
    }
}

The following calculates the area of a rectangle:

public class AreaCalculator
{
    public static int CalculateArea(Rectangle r)
    { 
        return r.Height * r.Width;    
    }
}

Now, the following returns the wrong results:

Example: LSP Violation
Rectangle sqr1 = new Square();
sqr1.Height = 6;
sqr1.Width = 8;
		
Console.WriteLine(AreaCalculator.CalculateArea(sqr1)); //returns 64
		
Rectangle sqr2 = new Square();
sqr2.Height = 8;
sqr2.Width = 6;
		
Console.WriteLine(AreaCalculator.CalculateArea(sqr2)); //returns 36

LSP says that the derived class should correctly implement the base class methods. Here, the square class is not a subtype of the rectangle class because it has equal sides. So, only one property is needed instead of two properties, height, and width. It creates confusion for the users of the class and might give the wrong result.