LSP Liskov Substitution Principle

If a program uses fuctionality from a base class, then substituting the base class to a subclass should not change this functionality. In other words, the client (caller) should not be aware of implementation hidden by this base class reference, whether it comes from base class or other derived classes.

This principle defines the rule of subclassing. Inheritance should not by only driven by data reuse, but a subclass must have the same behaviour as base class but implemented in some other way.

Suppose we have two types of bank account: current account and savings account. We could model this by base class CurrentAccount and SavingsAccount extending CurrentAccount. Suppose both of the accounts can be closed by boolean closeAccount(). The base class precondition to close the accont is that balance on this account is not negative, whereas for the savings account in order to close it there are two preconditions – not negative balance and account cannot be closed until minimum savings period passes. So for non-negative balance, the result of

CurrentAccount account = [ impl ]
boolean result = account.closeAccount();

could be different depending on the implementation, e.g. positive for CurrentAccount (fulfilled precodition) and negative for SavingsAccount (if second precodition fails – when not enough time passed to close the account). If the caller remembers the behavior of the base class, then the substitution to subclass would lead to unexpected results. This is because, the subclass SavingsAccout has more preconditions that base class CurrentAccount.

The rule of the thumb is that subclass shouldn’t have more strict preconditions than the base class.

The solution to this problem is break existing inheritance, define a separate interface and both of the specific implementations would directly implement that interface. In our case, we have interface Account with boolean closeAccount(). Both current and savings account would directly implement the interface (SavingsAccount would not be CurrentAcount anymore).

Another classic example could be the case of base class Rectange and subclass Square.
The Rectange has setters for width and height and a method area(). The Square‘s setHeight() implementation has a pre-requisite that it also sets width to the same value as heigth (and the same for setWidth()).
Then, if we call rectange.setWidth(2), rectange.setHeigth(5) and rectangle.area() then the result would be 2×5 if rectange is of type Rectange. If rectange is of type Square, the result is 2×2 (or 5×5). This leads to confusion.

When extending base class pay attention so that you leave subclass instance in an consistent state. The problems can be that the caller of polymorphic behavior may use overrided and non-overrided methods that change/use the object state. Pay attention that this state is changed constitently. Problems may appear e.g. when a non-overrided method changes the state and the overrided method introduces new state that does not relate to the state set/used by base class method.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s