DEV Community

Cover image for SOLID: L - Liskov Substitution Principle (LSP)
Paulo Messias
Paulo Messias

Posted on

SOLID: L - Liskov Substitution Principle (LSP)

Introduction to LSP:
The Liskov Substitution Principle (LSP) is the third principle in the SOLID set. It states that objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program. In other words, if class B is a subclass of class A, then we should be able to replace A with B without breaking the application. This principle ensures that a subclass can stand in for its superclass and behave correctly.

Objectives of LSP:

  • Ensure Substitutability: Subclasses should be substitutable for their base classes without altering the desirable properties of the program.
  • Promote Polymorphism: Encourages the use of polymorphism to create flexible and reusable code.
  • Enhance Code Reliability: Increases the reliability of code by guaranteeing that derived classes extend the behavior of base classes without introducing errors.
  • Facilitate Maintenance: Makes code easier to maintain by adhering to predictable class hierarchies.

Bad Practice Example (Classes):
Here we have a Rectangle class and a Square subclass. The Square class violates the LSP because it does not maintain the behavior of the Rectangle class.

class Rectangle {
  width: number;
  height: number;

  setWidth(width: number): void {
    this.width = width;
  }

  setHeight(height: number): void {
    this.height = height;
  }

  getArea(): number {
    return this.width * this.height;
  }
}

class Square extends Rectangle {
  setWidth(width: number): void {
    this.width = width;
    this.height = width;
  }

  setHeight(height: number): void {
    this.width = height;
    this.height = height;
  }
}
Enter fullscreen mode Exit fullscreen mode

In this approach, the Square class violates the LSP because setting the width also changes the height, which is not the expected behavior for a rectangle.

Good Practice Example (Classes):
To follow LSP, we can separate the concepts of Rectangle and Square into distinct classes that do not inherit from each other.

abstract class Shape {
  abstract getArea(): number;
}

class Rectangle extends Shape {
  width: number;
  height: number;

  constructor(width: number, height: number) {
    super();
    this.width = width;
    this.height = height;
  }

  getArea(): number {
    return this.width * this.height;
  }
}

class Square extends Shape {
  side: number;

  constructor(side: number) {
    super();
    this.side = side;
  }

  getArea(): number {
    return this.side * this.side;
  }
}
Enter fullscreen mode Exit fullscreen mode

Now, Rectangle and Square are both shapes but do not interfere with each other's behavior, maintaining the LSP.

Bad Practice Example (Functions):
Here we have a function that expects a Rectangle but misbehaves when passed a Square.

function calculateRectangleArea(rectangle: Rectangle): number {
  rectangle.setWidth(4);
  rectangle.setHeight(5);
  return rectangle.getArea();
}

const square = new Square();
console.log(calculateRectangleArea(square)); // Incorrect behavior
Enter fullscreen mode Exit fullscreen mode

Good Practice Example (Functions):
To follow LSP, we can ensure our functions work correctly with polymorphic types.

function calculateArea(shape: Shape): number {
  return shape.getArea();
}

const rectangle = new Rectangle(4, 5);
console.log(calculateArea(rectangle)); // Correct behavior

const square = new Square(5);
console.log(calculateArea(square)); // Correct behavior
Enter fullscreen mode Exit fullscreen mode

This approach ensures that the calculateArea function works correctly with both Rectangle and Square.

Conclusion:
Following the Liskov Substitution Principle ensures that derived classes or subclasses can stand in for their base classes without affecting the correctness of the program. This principle promotes the use of polymorphism and helps maintain a reliable and maintainable codebase. In React Native development with TypeScript, adhering to LSP results in components and classes that are predictable and easier to extend. Always ensure that your subclasses enhance, rather than alter, the behavior of their base classes to follow LSP effectively.

Top comments (0)