# SOLID - Liskov Substitution Principle

Solid Principles

In this tutorial we are going to learn about the Liskov Substitution Principle (LSP).

## Table of contents

GitHub repository

Reference

• S Single Responsibility Principle
• O Open-Closed Principle
• L Liskov Substitution Principle
• I Interface Segregation Principle
• D Dependency Inversion Principle

## Definition

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

• Subclass must not weaken post-condition and must not strengthen pre-condition.
• Exception types thrown by the method of the subclass must be same as the superclass or it must be subtypes of the method of the superclass.
• The parameter types of the method of the subclass must be same as the superclass or it must be more abstract than superclass.
• The return type of the method of the subclass must be same as the superclass or it must be a subtype of the method of the superclass.

### Pre-condition

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
}
}``````

### Post-condition

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
}
}``````

## Example

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);
}
}``````

## Problem

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.

## Solution

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

ADVERTISEMENT

ADVERTISEMENT

ADVERTISEMENT

ADVERTISEMENT

ADVERTISEMENT