The Liskov Substitution Principle (With Examples)
Take a look at this breakdown of the Liskov Substitution Principle and how following it ensures clean code that interacts well with itself.
Join the DZone community and get the full member experience.
Join For FreeThis article describes the Liskov Substitution Principle along with some examples in Java. Please feel free to make comments/suggestions if I missed some important points.
The following are the key points described in this article:
- What is the Liskov Substitution Principle (LSP)?
- Code Samples to Illustrate the LSP.
- What code quality characteristics are represented by the LSP?
What Is the Liskov Substitution Principle (LSP)?
Take a look at this paper on the Liskov Substitution Principle, which provides a lot of details on it. As per the LSP, functions that use references to base classes must be able to use objects of the derived class without knowing it. In simple words, derived classes must be substitutable for the base class. To illustrate the LSP, let’s take an example of rectangles and squares. One tends to establish the ISA relationship, thus, you can say that a square a rectangle. However, there arises a problem (hence, a violation of the LSP) which shall be demonstrated with the following code sample. Take a look at code illustration below to understand the LSP in detail.
Java Code Samples to Illustrate the LSP
The LSP is popularly explained using the square and rectangle example. Let’s assume we try to establish an ISA relationship between Square and Rectangle. Thus, we call “Square is a Rectangle.” The code below represents the relationship.
/**
* Rectangle
* @author Ajitesh Shukla
*/
public class Rectangle {
private int length;
private int breadth;
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public int getBreadth() {
return breadth;
}
public void setBreadth(int breadth) {
this.breadth = breadth;
}
public int getArea() {
return this.length * this.breadth;
}
}
Below is the code for Square. Note that Square extends Rectangle.
/**
* Square class; Square inherits from Rectangle;
* Represents ISA relationship - Square is a Rectangle
* @author Ajitesh Shukla
*/
public class Square extends Rectangle {
@Override
public void setBreadth(int breadth) {
super.setBreadth(breadth);
super.setLength(breadth);
}
@Override
public void setLength(int length) {
super.setLength(length);
super.setBreadth(length);
}
}
The code below represents the function which has Rectangle as an argument. As per the principle, the functions that use references to the base classes must be able to use objects of derived class without knowing it. Thus, in the example shown below, the function calculateArea, which uses the reference of “Rectangle,” should be able to use the objects of derived class, such as Square, and fulfill the requirement posed by Rectangle definition.
- Length must always be equal to the length passed as the input to method, setLength.
- Breadth must always be equal to the breadth passed as input to method, setBreadth.
- Area must always be equal to the product of length and breadth.
In this case, we try to establish an ISA relationship between Square and Rectangle such that calling “Square is a Rectangle” in the below code would start behaving unexpectedly if an instance of Square is passed. An assertion error will be thrown in the case of checking for "Area" and checking for "Breadth," although the program will terminate as the assertion error is thrown due to the failure of the Area check.
/**
* The class demonstrates the Liskov Substitution Principle (LSP)
*
* As per the principle, the functions that use references to the base classes must be able to use objects of derived class without knowing it.
* Thus, in the example shown below, the function calculateArea which uses the reference of "Rectangle" should be able to use the objects of
* derived class such as Square and fulfill the requirement posed by Rectangle definition.
*
* @author Ajitesh Shukla
*/
public class LSPDemo {
/**
* One should note that as per the definition of Rectangle, following must always hold true given the data below:
* 1. Length must always be equal to the length passed as the input to method, setLength
* 2. Breadth must always be equal to the breadth passed as input to method, setBreadth
* 3. Area must always be equal to product of length and breadth
*
* In case, we try to establish ISA relationship between Square and Rectangle such that we call "Square is a Rectangle",
* below code would start behaving unexpectedly if an instance of Square is passed
* Assertion error will be thrown in case of check for area and check for breadth, although the program will terminate as
* the assertion error is thrown due to failure of Area check.
*
* @param r Instance of Rectangle
*/
public void calculateArea(Rectangle r) {
r.setBreadth(2);
r.setLength(3);
//
// Assert Area
//
// From the code, the expected behavior is that
// the area of the rectangle is equal to 6
//
assert r.getArea() == 6 : printError("area", r);
//
// Assert Length & Breadth
//
// From the code, the expected behavior is that
// the length should always be equal to 3 and
// the breadth should always be equal to 2
//
assert r.getLength() == 3 : printError("length", r);
assert r.getBreadth() == 2 : printError("breadth", r);
}
private String printError(String errorIdentifer, Rectangle r) {
return "Unexpected value of " + errorIdentifer + " for instance of " + r.getClass().getName();
}
public static void main(String[] args) {
LSPDemo lsp = new LSPDemo();
//
// An instance of Rectangle is passed
//
lsp.calculateArea(new Rectangle());
//
// An instance of Square is passed
//
lsp.calculateArea(new Square());
}
}
Given the above code, what is the problem with Square-Rectangle ISA relationship?
- The Square class does not need methods like setBreadth or setLength, as the sides of a square are equal. This is wasteful. Imagine of hundreds of thousands of Square objects.
- The LSPDemo class would need to know the details of derived classes of Rectangle (such as Square) to code appropriately to avoid throwing error. The change in the existing code to take care of the derived class breaks the open-closed principle in the first place.
What Code Quality Characteristic Is Represented by LSP?
The following are some of the code quality characteristics that are represented by the Liskov Substitution Principle.
- It is only when derived types are completely substitutable for their base types that functions that use those base types can be reused with impunity, and the derived types can be changed with impunity.
- The LSP is also, at times, termed as “Design by Contract.” Using this scheme, methods of classes declare pre-conditions and post-conditions. The pre-conditions must be true in order for the method to execute. Upon completion, the method guarantees that the post-condition will be true.
- Design by Contract does influence the declaration of “throws” exceptions, as well as the throwing of runtime exceptions and try/catch in general.
Published at DZone with permission of Ajitesh Kumar, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments