top of page
Buscar

Usability of SOLID Principles with Kotlin - Liskov Substitution Principle: Part 3

Entering the third part of the SOLID article series with Kotlin, we will talk about the Liskov Substitution Principle.




The Liskov Substitution Principle (LSP) is the third of the SOLID principles of software design. This principle states that objects of a derived class should be able to replace objects of their base class without altering the functionality of the program. In other words, a subclass should be able to substitute a superclass without changing the system's behavior.


Importance of LSP

LSP is essential for ensuring code robustness and flexibility. When we follow LSP, we ensure that our class hierarchies are well-defined and that subclasses uphold the expectations set by their superclasses. This allows the code to be more reusable and testable, as well as easier to maintain and extend without introducing unexpected behaviors.


Implementing LSP in Kotlin

It's time to exemplify to facilitate our understanding of applying LSP. Let's consider a vehicle management system. We'll start with a base class Vehicle and subclasses for different types of vehicles, such as Car and Bicycle. Our goal is to ensure that all subclasses of Vehicle can be used interchangeably without breaking the expected behavior of the program.


Initial Implementation

First, we define our base class Vehicle and two subclasses Car and Bicycle:


open class Vehicle {
    open fun startEngine() {
        // Generic function to start the engine
    }

    open fun stopEngine() {
        // Generic function to stop the engine
    }
}

class Car : Vehicle() {
    override fun startEngine() {
        println("Car engine started")
    }

    override fun stopEngine() {
        println("Car engine stopped")
    }
}

class Bicycle : Vehicle() {
    // Bicycles don't have engines, so these functions don't make sense here
    override fun startEngine() {
        throw UnsupportedOperationException("Bicycles don't have engines")
    }

    override fun stopEngine() {
        throw UnsupportedOperationException("Bicycles don't have engines")
    }
}

In this implementation, Bicycle inherits from Vehicle but does not support the startEngine and stopEngine functions, which violates the LSP. Now, let's refactor our classes so that only vehicles with engines implement these functions.


Corrected Implementation

// Defining an interface for vehicles with engines
interface Engine {
    fun startEngine()
    fun stopEngine()
}

open class Vehicle {
    open fun ride() {
        // Generic function to ride the vehicle
    }
}

class Car : Vehicle(), Engine {
    override fun startEngine() {
        println("Car engine started")
    }

    override fun stopEngine() {
        println("Car engine stopped")
    }

    override fun ride() {
        println("Driving the car")
    }
}

class Bicycle : Vehicle() {
    override fun ride() {
        println("Riding the bicycle")
    }
}

Now, Bicycle does not inherit methods that don't make sense to implement. Car implements the Engine interface, indicating that it has an engine.


Conclusion

Following the Liskov Substitution Principle is crucial for creating flexible and robust software systems. By refactoring our classes to ensure that subclasses can replace their superclasses without altering the expected behavior, we promote more reusable and maintainable code. In Kotlin, we can effectively use interfaces and inheritance to adhere to this principle, ensuring that our system is modular and adaptable to future changes.


 
 
 

Comments


  • Linkedin
  • GitHub

© 2024 by Thalles Vieira All Rights Reserved

Subscribe for me!

Thanks for submitting!

bottom of page