SOLID : liskov substitution
Tags: typescript, programming, architecture
Published: 2022-09-12 18:11:31

definiton of liskov substitution is like that
> 🤔 The Liskov Substitution Principle (LSP) states that subtypes must be substitutable for their base types

But simply put, it would be just
> 😀 🎉🎉🎉🎉 follow the rules of parent class !🎉🎉🎉🎉

I'm gonna show you sample code, but don't forget this simple definition that I created

## 👎 Bad code

You want to drink water, and make **MountainWater** and **RiverWater**, then drink them

```typescript
interface IWater {
  shower: () => void
  drink: () => void
  wash: () => void
}

class Water implements IWater{
  drink() {
    console.log('drink it !!')
  }
  shower() {
    console.log('take a shower!!')
  }
  wash() {
    console.log('wash somewhere with it !!')
  }
}

class MountainWater extends Water {
  drink() {
    console.log('drink it !! you even feel like were are in mountain !!')
  }
}

class RiverWater extends Water {
  // 🔥🔥🔥shouldn't change parent class rule !!🔥🔥🔥
  drink() {
    throw new Error('Do not drink it !! this is dirty !!!!! dangerous !!!!')
  }
  shower() {
    console.log('you could take a shower with it, but be careful')
  }
}

const makeWaterDrink = (water: IWater) => {
  water.drink()
}

const waterInEverest = new MountainWater()
const waterFromTap = new RiverWater()

makeWaterDrink(waterInEverest)
makeWaterDrink(waterFromTap)

// drink it !! it is pure
// throw new Error('Do not drink it !! this is dirty !!!!! dangerous !!!!')
```

Problem in this code is that `drink() function` should be `void`, but you `throw error`.
Yeah you don't follow rules of parent class !!

## 👍 Good code
```typescript
// ⭐⭐ Divide interfaces 
interface IPureWater {
  shower: () => void
  drink: () => void
}

interface IDirtyWater {
  shower: () => void
  wash: () => void
}

class PureWater implements IPureWater {
  shower() {
    console.log('you can take a shower with it normally !!')
  }
  drink() {
    console.log('drink it !!')
  }
}

class DirtyWater implements IDirtyWater {
  shower() {
    console.log('you could take a shower, but be careful')
  }
  wash() {
    console.log('you can wash somewhere')
  }
  // ⭐⭐ You don't need to write about drink() ⭐⭐
  // because you divided interfaces
}

class MountainWater extends PureWater{
  drink() {
    console.log('drink it !!!  you even feel like you were in mountain !!')
  }
}

class RiverWater extends DirtyWater{}

const makeWaterDrink = (water: IPureWater) => {
  water.drink()
}

const makeWashWater = (water: IDirtyWater) => {
  water.wash()
}

const waterInEverest = new MountainWater()
const waterFromTap = new RiverWater()

makeWaterDrink(waterInEverest)
makeWashWater(waterFromTap)

// drink it !!!  you even feel like you are in mountain !!
// you can wash somewhere
```

Thanks for dividing interface from just `Iwater` to `IPureWater` and `IDirtyWater`, all classes follow parent class rule now !!

This is simple **liskov substitution** sample 

---

> 😀 follow the rules of parent class !

(Someone wouldn't agree with that, because it's too simple, but for beginners it's enough, I think)

ref
https://youtu.be/dJQMqNOC4Pc?t=324
https://www.membersedge.co.jp/blog/typescript-solid-liskov-substitution-principle/