SOLID — From Zero To Potato 3

About Liskov Substitution Principle (LSP)

YE MON KYAW
Arpalar Tech
3 min readMar 30, 2023

--

Today I will continue explain about Liskov Substitution Principle (LSP) in this article , you can read my previous article about Single-responsibility principle and Open/Closed Principle (OCP) there.

Before moving to the detail explain about LSP, we should know how LSP helpful in our daily jobs. LSP helps us model good inheritance hierarchies. It helps us prevent model hierarchies that don’t conform to the Open/Closed principle. Any inheritance model that adheres to the Liskov Substitution Principle will implicitly follow the Open/Closed principle.

Let jumps to the coding, by the way if you don’t understand about kotlin don’t worry LSP can implement in any language so you can try with your favourite one.

We have a Student class that has a calculateGPA method that calculates the GPA based on the student's grades:

open class Student(val grades: List<Double>) {
open fun calculateGPA(): Double {
// calculate the GPA based on the grades
}
}

After that we want to create a HonorsStudent subclass that extends Student and calculates the GPA based on a different set of rules:

class HonorsStudent(grades: List<Double>) : Student(grades) {
override fun calculateGPA(): Double {
// calculate the GPA based on a different set of rules for honors students
}
}

How do you think above new class HonorsStudent class , it seems fine right? seem does not break ‘OCP’. But actually HonorsStudent subclass violates the Liskov Substitution Principle because it changes the behavior of the calculateGPA method from the base Student class. This can cause problems if code that expects a Student object is passed a HonorsStudent object, as the behavior of the calculateGPA method is now different.

To adhere to the LSP, we can modify the design of the classes. One way to do this is to create a new interface called GPA that has a calculate method:

interface GPA {
fun calculate(): Double
}

open class Student(val grades: List<Double>) : GPA {
override fun calculate(): Double {
// calculate the GPA based on the grades
}
}

class HonorsStudent(grades: List<Double>) : GPA {
override fun calculate(): Double {
// calculate the GPA based on a different set of rules for honors students
}
}

In this refactored version, we create a separate interface for the calculateGPA method, called GPA. The Student and HonorsStudent classes both implement this interface, and each provides its own implementation of the calculate method.

This adheres to the Liskov Substitution Principle because any code that expects an object of type GPA can be passed either a Student object or a HonorsStudent object, and the behavior of the calculate method will be consistent across both objects.

By separating out the calculateGPA behavior into a separate interface, we ensure that any subclass that implements that interface behaves consistently with other objects of that interface type.

Thank you for taking the time to read this article. I hope that my writing has been informative and thought-provoking.

--

--

YE MON KYAW
Arpalar Tech

Software Engineer who write code in Kotlin / Android