Solid Principles
In this tutorial we are going to learn about the Liskov Substitution Principle (LSP).
Reference
Liskov Substitution Principle (LSP) states that objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program. Violating this principle can lead to unexpected behavior and errors.
Rules for the methods of the subclasses
It is a condition that must be true before a function (method) is executed. So, by pre-condition a method tells the client that it is expecting something to be in place before it can execute.
In the following example we have the add
method that takes two integer
values. So, the pre-condition is that the two values passed must be valid integer values. If they are not then we will get error and the method will not execute.
class MathOps {
public int add(int x, int y) {
return x + y;
}
}
class MyClass {
public static void main(String[] args) {
MathOps obj = new MathOps();
System.out.println(obj.add(1, "A")); // error: incompatible types: String cannot be converted to int
}
}
It is a condition that will be true after a function (method) is executed and if the pre-condition is true. So, by post-condition a method tells the client that it promises to do something provided the pre-condition is true and the method has completed its execution.
In the following example we have the add
method that takes two integer
values. The post-condition for this method is that it will always return an integer value when the add method completes its exectution provided the pre-condition is true i.e., the two arguments passed to the add method are also valid integer values.
class MathOps {
public int add(int x, int y) {
return x + y;
}
}
class MyClass {
public static void main(String[] args) {
MathOps obj = new MathOps();
System.out.println(obj.add(1, 2)); // 3
}
}
In the following JAVA program we have the Square class that extends the Rectangle class.
class Rectangle {
protected int width;
protected int height;
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getArea() {
return width * height;
}
}
class Square extends Rectangle {
public Square(int side) {
super(side, side);
}
@Override
public void setWidth(int width) {
super.setWidth(width);
super.setHeight(width);
}
@Override
public void setHeight(int height) {
super.setWidth(height);
super.setHeight(height);
}
}
Test
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class LSPTests {
@Test
@DisplayName("This will break LSP")
public void shouldBreakLSP() {
Rectangle rectangle = new Square(10);
rectangle.setHeight(10);
rectangle.setWidth(20);
Assertions.assertNotEquals(rectangle.getArea(), 200);
}
}
In the Square
class we are overriding the setHeight
and setWidth
methods. So, setting the height of a Square also changes the width. Due to this the Square class violates the Liskov Substitution Principle as we can no longer use the Square object in place of the Rectangle.
In order to fix this problem we have to remove the "is-a" relationship between Square and Rectangle classes. A better solution is to create an interface Shape and both Square and Rectangle can implement it.
interface Shape {
public double getArea();
}
class Square implements Shape {
protected int side;
public Square(int side) {
this.side = side;
}
public int getSide() {
return side;
}
public void setSide(int side) {
this.side = side;
}
@Override
public double getArea() {
return side * side;
}
}
class Rectangle implements Shape {
protected int width;
protected int height;
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
@Override
public double getArea() {
return width * height;
}
}
Test
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class LSPTests {
@Test
@DisplayName("This will not break LSP")
public void shouldNotBreakLSP() {
Shape rectangle = new Rectangle(10, 20);
Shape square = new Square(10);
Assertions.assertEquals(rectangle.getArea(), 200);
Assertions.assertEquals(square.getArea(), 100);
}
}
ADVERTISEMENT