SOLID Principles – Liskov Substitution Principle [LSP]

SOLID stands for Single Responsibility Principle [SRP], Open Closed Principle [OCP], Liskov Substitution Principle [LSP], Interface Segregation Principle [ISP] and Dependency Inversion Principle [DIP].

In this article I will explain Liskov Substitution Principle [LSP]. This pattern states that functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it. In other words you should always be able to use a base class or interface instead of the actual implementation and still get the expected result. If you can't, you’re breaking LSP.

Lets understand it through example.

In below code example I have defined base class Account and two derived classes SavingAccountand CheckingAccount. In UML notation SavingAccount and CurrentAccount is a type of Account. The Withdraw method defined in Bank class accepts base class Account as a parameter and amount to withdraw.

namespace LSP_Bank
{
    using System;

    public class Program
    {
        static void Main(string[] args)
        {
            Bank bank = new Bank();
            Account acc = new Account(1);
            bank.Withdraw(acc, 100);

            Account saving = new SavingAccount(2);
            bank.Withdraw(saving, 100);
        }
    }

    public class Bank
    {
        public virtual void Withdraw(Account acc, int amount)
        {
            acc.Withdraw(acc.Id, amount);
        }
    }

    public class Account
    {
        public Account(int AccountId)
        {
            this.Id = AccountId;
        }

        public virtual int Id { get; set; }

        public virtual void Withdraw(int accountId, int amount)
        {
            Console.WriteLine("In base withdraw");
        }
    }

    public class SavingAccount : Account
    {
        public SavingAccount(int savingAccountId)
            : base(savingAccountId)
        {

        }

        public override void Withdraw(int accountId, int amount)
        {
            Console.WriteLine("In SavingAccount withdraw");
        }
    }

    public class CurrentAccount : Account
    {
        public CurrentAccount (int currentAccountId)
            : base(currentAccountId)
        {

        }
        public override void Withdraw(int accountId, int amount)
        {
            Console.WriteLine("In CurrentAccount withdraw");
        }
    }
}

In the calling program, first we are passing Account class instance to Withdraw method using below code

Bank bank = new Bank();
Account acc = new Account(1);
bank.Withdraw(acc, 100);

However, as LSP states, the program should be able to use the objects of derived class without breaking any behavior of base class. So in below code statement, I am passing a SavingAccount instance to Withdraw method, which executes the function defined in Account class. So we are adhering to LSP pattern here.

Bank bank = new Bank();
Account saving = new SavingAccount(2);
bank.Withdraw(saving, 100);

Square - Rectangle Analogy

LSP can be confusing in some cases. Lets take a look at below example in which I have defined a base class Rectangle and a derived class Square. The CalculateArea method defined in Rectangle class is used to calculate area for both Rectangle and Square.

In below code, I am setting the height and width of a Rectangle and then calling CalculateArea method. In this case it will display correct area value as 20.

Rectangle rect = new Rectangle();
rect.SetHeight(10);
rect.SetWidth(2);
System.Console.WriteLine(rect.CalculateArea());

However, lets assume we are instantiating a Rectangle using some factory method, in which case we just know the factory method will return a Rectangle. In below example, we assumed that factory method has returned a Square. Now, the SetHeight method defined in Square class sets both height and width of the Square. Thus the CalculateArea function in below example will display output as 25 rather than expected 50.

Rectangle rect1 = new Square(); 
rect1.SetHeight(10);
rect1.SetWidth(5);
System.Console.WriteLine(rect1.CalculateArea());

Source Code

namespace LSP_Rectangle
{
    public class Program
    {
        static void Main(string[] args)
        {
            Rectangle rect = new Rectangle();
            rect.SetHeight(10);
            rect.SetWidth(2);
            System.Console.WriteLine(rect.CalculateArea());
            
            // Below instantiation can be returned by some factory method
            Rectangle rect1 = new Square(); 
            rect1.SetHeight(10);
            rect1.SetWidth(5);
            System.Console.WriteLine(rect1.CalculateArea());
        }
    }

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

        public virtual void SetHeight(int height)
        {
            this.Height = height;
        }

        public virtual void SetWidth(int width)
        {
            this.Width = width;
        }

        public virtual int CalculateArea()
        {
            return this.Height * this.Width;
        }
    }

    public class Square : Rectangle
    {
        public override void SetHeight(int height)
        {
            this.Height = height;
            this.Width = height;
        }

        public override int CalculateArea()
        {
            return base.CalculateArea();
        }
    }
}

I hope this article was helpful to you. In next article, I will explain Interface Segregation Principle [ISP].

Thanks.

Download Source Code

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>