Liskov Substitution Principle In Typescript: Explained
Tags: oop, typescript
Published: 2023-10-13 15:09:35

## Introduction
Liskov Substitution Principle is the third principle in `S.O.L.I.D` principles. It was introduced by Barbara Liskov in 1987. It is a principle in object-oriented programming that states that if a program is using a base class, then the reference to the base class can be replaced with a derived class without affecting the functionality of the program.

## Problem

Let's say we have a `Rectangle` class that has a `width` and `height` properties and a `getArea` method that returns the area of the rectangle.

```ts
class Rectangle {
  constructor(public width: number, public height: number) {}

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

Now we want to create a `Square` class that extends the `Rectangle` class. The `Square` class has a `size` property and overrides the `getArea` method to return the area of the square.

```ts
class Square extends Rectangle {
  constructor(public size: number) {
    super(size, size);
  }

  getArea(): number {
    return this.size * this.size;
  }
}
```

Now let's create a function that takes a `Rectangle` object and prints its area.

```ts
function printArea(rectangle: Rectangle) {
  console.log(rectangle.getArea());
}
```

Now let's create a `Rectangle` object and pass it to the `printArea` function.

```ts
const rectangle = new Rectangle(2, 3);

printArea(rectangle); // 6
```

Now let's create a `Square` object and pass it to the `printArea` function.

```ts
const square = new Square(2);

printArea(square); // 4
```

As you can see, the `printArea` function works fine with both `Rectangle` and `Square` objects. But what if we want to create a `Square` object and pass it to the `printArea` function as a `Rectangle` object?


Although this works fine, but we had to override the `getArea` method in the `Square` class to make it work. This is a violation of the Liskov Substitution Principle. The `Square` class is not a proper substitute for the `Rectangle` class.

## Solution

To make it properly addressed, we can do it using base class.

### Using A Shape as Base Class

Let's make a base class `Shape` this is basically an abstract class with abstract `getArea` method.

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

Now let's make the `Rectangle` and `Square` classes extend the `Shape` class.

```ts
class Rectangle extends Shape {
  constructor(public width: number, public height: number) {
    super();
  }

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

class Square extends Shape {
  constructor(public size: number) {
    super();
  }

  getArea(): number {
    return this.size * this.size;
  }
}
```

Now let's create a function that takes a `Shape` object and prints its area.

```ts
function printArea(shape: Shape) {
  console.log(shape.getArea());
}
```

### Another way to solve this problem

We can also solve this problem by using an interface.

```ts
interface Shape {
  getArea(): number;
}
```

Now let's make the `Rectangle` and `Square` classes implement the `Shape` interface.

```ts
class Rectangle implements Shape {
  constructor(public width: number, public height: number) {}

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

class Square implements Shape {
  constructor(public size: number) {}

  getArea(): number {
    return this.size * this.size;
  }
}
```

Now let's create a function that takes a `Shape` object and prints its area.

```ts
function printArea(shape: Shape) {
  console.log(shape.getArea());
}
```

## More Example

Imagine that we have a `Vehicle` class that has a `fuelUp` and `maxFuelCapacity` methods

```ts
class Vehicle {
    public fuelUp(): string {
        return 'Fueled';
    }

    public maxFuelCapacity(): number {
        return 100;
    }
}
```

Now Let's create `Car` class which inherits the `Vehicle` class, pretty much the same methods in our case.

```ts
class Car extends Vehicle {
    // nothing to do
}
```

Let's use it with a function `fuelUpVehicle` that takes a `Vehicle` object and calls the `fuelUp` method.

```ts
function fuelUpVehicle(vehicle: Vehicle): void {
    vehicle.fuelUp();
}
```

Now let's create a `Car` object and pass it to the `fuelUpVehicle` function.

```ts
const car = new Car();

fuelUpVehicle(car);
```

So far so good, now let's create another vehicle, a `ElectricCar` this time, which also inherits the `Vehicle` class, but it doesn't need the `fuelUp` method, because it doesn't use fuel.

```ts
class ElectricCar extends Vehicle {
    public fuelUp(): string {
        throw new Error('Electric car does not need fuel');
    }

    public maxFuelCapacity(): number {
        return 0;
    }
}
```

If we tried to use the same function `fuelUpVehicle` with the `ElectricCar` object, it will throw an error.

```ts
const electricCar = new ElectricCar();

fuelUpVehicle(electricCar); // Error: Electric car does not need fuel
```

This violates the `Liskov Substitution Principle` because `ElectricCar` is not a proper `Substitution` for the `Vehicle` car.

Let's fix this behavior by changing the `ElectricCar` class

```ts
class ElectricCar extends Vehicle {
    public fuelUp(): string {
        return 'Electric car is charged';
    }

    public maxFuelCapacity(): number {
        return 100; // this is the battery capacity in percentage not in liters
    }
}
```

Now if we tried to use the same function `fuelUpVehicle` with the `ElectricCar` object, it will work fine.

```ts
const electricCar = new ElectricCar();

fuelUpVehicle(electricCar); // Electric car is charged
```

## Conclusion

To address `Liskov Substitution Principle (LSP)` we can say that the base class should be able to be substituted with any of its derived classes without affecting the functionality of the program.

In other words, Subtypes must be substitutable for their base types. In simpler terms, if a piece of code works with objects of the base class, it should work equally well (without surprises) with objects of any derived class.


## Following the list

You can see the updated list of design principles from the following link
https://mentoor.io/en/posts/634524154/open-closed-principle-in-typescript

Join us in our Discord Community
https://discord.gg/XDZcTuU8c8