The Liskov Substitution Principle dictates when and where it is correct to derive a subtype from an existing supertype. As defined by Barbara Liskov and Jeannette Wing, it essentially states that:
Let q(x) be a property provable about objects x of type T. Then q(y) should be true for objects y of type S where S is a subtype of T.
What it means is that if S is a subtype of T, then a function q(x) must behave in the same manner irrespective of the type of x whether it be S or T. In other words, if a piece of code behaves differently for a subtype than a supertype, then that code violates the LSP (and consequently the OCP).
Let’s say that we work for a bank and that we have a simple teller application. Currently the teller application works with checking and savings account types.
public abstract class Account
{
public abstract double CurrentBalance { get; }
public abstract void Deposit(double amount);
}
public class CheckingAccount : Account
{
private double _currentBalance;
public override double CurrentBalance
{
get { return _currentBalance; }
}
public override void Deposit(double amount)
{
_currentBalance += amount;
}
}
public class SavingsAccount : Account
{
/* Savings account implementation */
}
Our bank just entered the mortgage business so we need to modify our teller application to support this new account type. During the short analysis phase we decide that since mortgage account is type of an account (IS-A relationship), we can simply create a new mortgage class deriving from the Account abstract class, override a method and a property, and we should be in business. We add a new class as follows:
public class MortgageAccount : Account
{
private double _currentBalance;
public override double CurrentBalance
{
get { return _currentBalance; }
}
public override void Deposit(double amount)
{
_currentBalance -= amount;
}
}
Everything sounds logical, that is until we come across this code in the teller application:
public class Bank
{
public void ReceiveMoney(Account account, double amount)
{
double oldBalance = account.CurrentBalance;
account.Deposit(amount);
Debug.Assert(account.CurrentBalance = oldBalance + amount);
}
}
Based on our pre-existing code, the ReceiveMoney() method is making a reasonable assumption that the new balance should be equal to the old balance plus the newly deposited amount. This assumption is, however, violated if we pass an instance of the Mortgage class to this method. This is a clear violation of the LSP.