DEV Community

Kaziu
Kaziu

Posted on

SOLID : liskov substitution

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

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 !!!!')
Enter fullscreen mode Exit fullscreen mode

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

// ⭐⭐ 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
Enter fullscreen mode Exit fullscreen mode

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/

Top comments (0)