3. Liskov Substitution in [S.O.L.I.D] Principles.

3. Liskov Substitution in [S.O.L.I.D] Principles.

·

3 min read

Introduction:

-- In this series, I will explain the five rules of S.O.L.I.D principles in detail.
The reason these are important is so that, your code will be readable, maintainable, and testable.

You can find the previous chapters of the series below:

1. [S] in [S.O.L.I.D] Single Responsibility
2. [O] in [S.O.L.I.D] Open-Closed

Let`s go through the third principle [Liskov Substitution]:

  • From the previous open/closed principle this rule focus on the behavior of superclass and its subtypes, as I will explain in this article.

Defenition from software perspective:

If Class B is a subtype that means [Extened from it] of Class A then, objects of (A) can be replaced with objects of (B), without altering any of the desirable properties of the program.

  • Class A is the parent class.
  • Class B is the subclass (child class).

Let`s validate it with a code example to understand it well.

Assume we have a Rectangle Class with properties width, height, and area as we create an object from this class we pass the height and width of this rectangle to get its` area.

class Rectangle {
    var width: Int = 0
    var height: Int = 0

    constructor()

    constructor(width: Int, height: Int) {
        this.width = width
        this.height = height
    }

    val area: Int
        get() = width * height
}


val rec: Rectangle = Rectangle(width = 2, height = 5)

println(rec.area) // Prints -> 10

Then we decided to create another class Square Class as it`s a shape we can extend the Rectangle Classand override its functionality.

First, we need to make Rectangle Class extendable as in Kotlin class is final by default.

open class Rectangle {
    open var width: Int = 0
    open var height: Int = 0
    .....
}
class Square : Rectangle {

    override var width: Int
        get() = super.width
        set(width) {
            super.width = width
            super.height = width
        }

    override var height: Int
        get() = super.height
        set(height) {
            super.height = height
            super.width = height
        }

    constructor() {}

    constructor(size: Int) {
        height = size
        width = height
    }
}

val square = Square(size = 5)

println(square.area) // Prints -> 25

Until this point we are good everything is working as expected, the issue will appear when we try to initialize the rectangle object with square class.

It will fail our expectation because Square Class sets the height and width with the same value.

val rectangle : Rectangle = Square()
    rectangle.height = 2
    rectangle.width = 5
    println(rectangle.area)

/**
 * Prints -> 25
 * Expected -> 10
 * rectangle.height = 2  (sets the width also with 2)
 * rectangle.width = 5 (sets the height also with 5)
 */

In the above example, we break this rule because we can`t replace the superclass object with its subtype object.

So, I will give you a simple solution to apply this rule. Make your subtype class calls the super constructor.

class Square : Rectangle {

    constructor() {}

    constructor(size: Int) : super(size, size)
}

val rectangle: Rectangle = Square()
    rectangle.height = 2
    rectangle.width = 5
    println(rectangle.area)

/**
     * prints -> 10
     * it will make the height and width with the same value.
     */
  • Below I will attach the final code to achieve this principle. liskov.png

Finally, I hope that you understand this principle.

Happy coding 😀🎉
If you enjoyed this blog post ♥, follow✔, and let`s share the knowledge LinkedIn