Liskov Substitution Principle (LSP) - SOLID
🧱

Liskov Substitution Principle (LSP) - SOLID

Tags
Clean Code
Javascript
Software Development
SOLID
Published
Published September 22, 2022
Author
Due Date

The SOLID Principles

SOLID is an acronym for five other class-design principles:
  • Interface Segregation Principle
  • Dependency Inversion Principle.

The Liskov Substitution Principle (LSP)

☑️ Topic: Objects and Data Structures
 
☑️ Idea: If S is a subtype of T, then objects of type T may be replaced with objects of type S. If you have a parent class and a child class, then the child class can be used where the base class is accepted without getting incorrect results.
 
☑️ Benefits: Re-usability, Testability.
 
☑️ Guideline: If your interface works for a class it should also work for other classes than inherit from it.
 

Benefits Explained

  • Low dependence: Code can be used with a class or another and that lowers the dependence.
  • Better abstractions: It’s a test that forces you to think of better abstractions for your code.
  • Fewer changes: If a function can be used for multiple classes then there is only one thing to maintain.
 

Simple example

A simple example can be demonstrated with birds.
Ducks can fly, but ostrich cannot, and they are both birds.
 

BAD

public class Bird{ public void fly(){} } public class Duck extends Bird{} // Ducks can fly public class Ostrich extends Bird{} // Ostriches also have a `fly` method, but ostriches can't fly.

GOOD

public class Bird{} public class FlyingBirds extends Bird{ public void fly(){} } public class Duck extends FlyingBirds{} // Ducks can fly public class Ostrich extends Bird{} // Ostrich can't fly, but they are still birds

Complex example

Let’s look at a design example with squares and rectangles.
If the designer makes Square inherit from Rectangle using the “is a” rule (Squareis a Rectangle), then the design can violate the Liskov substitution principle.

BAD

// BAD class Rectangle { constructor() { this.width = 0; this.height = 0; } render(area) { /* ... */ } setWidth(width) { this.width = width; } setHeight(height) { this.height = height; } getArea() { return this.width * this.height; } } class Square extends Rectangle { // Square "is a" Rectangle setWidth(width) { this.width = width; this.height = width; } setHeight(height) { this.width = height; this.height = height; } } function renderLargeRectangles(rectangles) { // `rectangle` can be a Square rectangles.forEach(rectangle => { rectangle.setWidth(4); rectangle.setHeight(5); const area = rectangle.getArea(); // BAD: Returns 25 for Square. Should be 20. rectangle.render(area); }); }
 

GOOD

// GOOD class Shape { render(area) {/* ... */} } class Rectangle extends Shape { constructor(width, height) { super(); this.width = width; this.height = height; } getArea() { return this.width * this.height; } } class Square extends Shape { constructor(length) { super(); this.length = length; } getArea() { return this.length * this.length; } } function renderLargeShapes(shapes) { // Works for Shape and its descendants shapes.forEach(shape => { const area = shape.getArea(); shape.render(area); }); }