Liskov principle is one of SOLID principles. Common definition of this principle is:
Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
or
Inheritance should only be used if the derived class is truly a more specialized version of the base class.
But how can we use this principle in practice?
In practice we use inheritance. We create base class and it’s derivatives. Derivatives have properties and methods of base class.
I’ll give you an example. You have a base class Account and derived classes CheckingAccount, SavingAccount and AutoLoanAccount.
class Account {
let rate: Double
var accountNumber: String {
"\(Self.self)"
}
init(rate: Double) {
self.rate = rate
}
func interestRate() -> Double {
rate
}
}
class CheckingAccount: Account {
override var accountNumber: String {
"\(Self.self)"
}
}
class SavingsAccount: Account {
override var accountNumber: String {
"\(Self.self)"
}
}
class AutoLoanAccount: Account {
override var accountNumber: String {
"\(Self.self)"
}
}
LSP claims that you can use CheckingAccount instead of Account and there will be no difference for programmer. So when you call any method from any derivatives of Account class, programmer shouldn’t care about the object’s subtype.
For example, you call interestRate() method from each of classes. If I call interestRate() method in CheckingAccount and SavingAccount, it returns percent that is payed by bank to client. In case of AutoLoanAccount method returns percent that is payed by client to bank and the sign of the result must be changed to minus. Programmer must be mindful of weather bank pays to client or on the contrary. It means that I can’t use AutoLoanAccount instead of Account, because I will have to change sign of interest paid. In other words, all methods of the base class must have the same meaning in each derived class.
if programmer must be mindful of semantic differences in subclass implementations then Liskov principle is violated.
According to LSP, class AutoLoanAccount should not be a derivative from Account class, because methods interestRate() in these classes have different semantic meaning. AutoLoanAccount can be a derivative from LoanAccount class.
class LoanAccount {
let rate: Double
var accountNumber: String {
"\(Self.self)"
}
init(rate: Double) {
self.rate = rate
}
func interestRate() -> Double {
-rate
}
}
class AutoLoanAccount: LoanAccount {
override var accountNumber: String {
"\(Self.self)"
}
}
If you follow LSP, inheritance is a powerful means of reducing complexity, allowing the programmer to focus on the general attributes of an object without worrying about the details. If the programmer must constantly think about the semantic differences between subclass implementations, inheritance increases complexity.