SOLID Design Principles (LSP)

Abdullah Aimen
4 min readApr 5, 2019
Building a wall in the right way retains its strength

In the previous article, we talked about the second Principle of SOLID Principles series. we demonstrated an example to make everything clear and obvious.

Today, we are going to talk about the third principle…

Liskov-Substitution Principle (LSP)

Derived Classes must be usable through the base class interface without the need for the user to know the difference.

In other words, Derived types must be completely substitutable for their base types. If S is a subtype of T, then objects of type T may be replaced with objects of type S.

So, How we can apply the LSP?

  • We should also ensure that Clients should not know which specific sub-type they are calling, nor should they need to know that. The client should behave the same regardless of the sub-type instance that it is given.
  • No new exception can be thrown by the sub-type unless they are part of the existing exception hierarchy.
  • New derived classes just extend without replacing the functionality of old classes.

Let’s have an example to demonstrate the LSP,…

open class User(var name: String, var password: String) {
private lateinit var email: String

fun login(name: String, password: String) {
this.name = name
this.password = password
System.out.println("Current LoggedIn User: $name")
}

fun register(name: String, password: String, email: String) {
this.name = name
this.password = password
this.email = email
System.out.println("User : $name Register Successfully!")
}

open fun logError(error: String) {
System.out.println(error)
}

fun sendEmail(emailText: String) {
System.out.println(emailText)
}
}

the upper class creates a class with some property and given functionality to the created user. If we need to take a sub-class of it which means…

class ContractUser(var cName: String, var cPassword: String) : User(cName, cPassword) {

fun setWorkedHours(h: Int) {
System.out.println("$cName worked :$h this month")
}

/**
* as contract user it means that i can not send or log error in the system
* so, the only available functionality for me to login and register.
* and the other function will throw a NotImplementedException!
* */
override fun logError(error: String) {
throw NotImplementedError(error)
}
}

And the implementation will be like…

fun main(args: Array<String>) {
var mUser: User = User("Jack", "123456")

mUser.login("Jack", "123456")
mUser.sendEmail("sending a new email to employee id #100")

System.out.println()
System.out.println("##### contract user #####")
var mContractUser: ContractUser =
ContractUser("Ali", "010000")
mContractUser.setWorkedHours(45)

/**
* if we call logError it will throw a notImplementedException
* */
//mContractUser.logError("error")
}

resolving the issue by throwing an exception is a very bad workaround solution. which will be more complicated when scaling up the system!

The Right way is…

open class User(private var name: String) {
private lateinit var email: String
private lateinit var password: String

fun register(name: String, password: String, email: String) {
this.name = name
this.password = password
this.email = email
System.out.println("User : $name Register Successfully!")
}

fun login(name: String, password: String) {
this.name = name
this.password = password
System.out.println("Current LoggedIn User: $name")
}

}
interface ISendEmail {
fun sendEmail(emailText: String)
}
interface Ilogger {
fun logError(error: String)
}
class NormalUser(private var Nname: String) : User(Nname),
ISendEmail {
override fun sendEmail(emailText: String) {
System.out.println("email from $Nname sent! \n $emailText")
}

}
class AdminUser(private var adminName: String) : User(adminName),
Ilogger,
ISendEmail {
override fun logError(error: String) {
System.out.println(error)
}

override fun sendEmail(emailText: String) {
System.out.println("email from $adminName send! \n $emailText")
}
}

And the implementation will be like :

fun main(args: Array<String>) {
//create new user with basic functionality
var mBasic = User("basic user")
mBasic.register("new Basic User", "123456", "newUser@gmail.com")
mBasic.login("basic user", "123456")

System.out.println()
System.out.println()
//create new normal user with send email functionality
var mSendEmailUser = NormalUser("normal user")
mSendEmailUser.register("new NormalUser User", "123456", "newNormalUser@gmail.com")
mSendEmailUser.login("NormalUser user", "123456")
mSendEmailUser.sendEmail("hi, please come to meeting room.")

System.out.println()
System.out.println()

//create new admin user with send email functionality
var mAdminUser = AdminUser("admin user")
mAdminUser.register("new admin User", "123456", "newadmin@gmail.com")
mAdminUser.login("admin user", "123456")
mAdminUser.sendEmail("hi, please come to meet admin in meeting room.")
mAdminUser.logError("busy with a task")
}

as we see from the implementation there is no throwing exception or try and catch the exception. so, if we scale up and add new functionality throw new interfaces it will be easy without touching any of the old classes. This allows us to use good object-oriented principles like encapsulation and reuse and doesn’t violate LSP.

Summary

LSP is necessary where some code thinks it is calling the methods of a type T, and may unknowingly call the methods of a type S, where S extends T (i.e. S inherits, derives from, or is a subtype of, the supertype T).

Remember always you are a Programmer, not Plumper! Thanks for continuing the article. If you like it just clap and share!

see you at the fourth principle!

References

Object-Oriented Programming

SOLID Principles

--

--