SOLID Principles in Kotlin: Theory and Practice for Clean Code

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: Why SOLID is Important in Kotlin?



SOLID is an acronym for five fundamental principles of object-oriented programming formulated by Robert Martin. In the context of Kotlin, a modern multiplatform language, these principles take on special significance. Kotlin combines both OOP and functional capabilities, allowing you to write more flexible and maintainable code. Adhering to SOLID principles helps developers create systems that are easy to scale, test, and maintain.

In this article, we will break down each of the five principles using specific examples in Kotlin, from theory to practical implementation. You will learn how to avoid typical antipatterns and make your code cleaner.



1. Single Responsibility Principle (SRP)

Theory: Each class should have only one reason to change. In other words, a class should be responsible for only one functionality or task.

Practice in Kotlin: Developers often violate SRP by placing data processing logic, validation, and work with external services into one class.

Example of violation:

class UserManager {    fun saveUser(user: User) {        // Saving to database        println("User saved to DB")    }

fun sendEmail(user: User, message: String) { // Sending email println("Email sent to ${user.email}") }

fun validateUser(user: User): Boolean { // Data validation return user.name.isNotEmpty() }}

Here, UserManager is responsible for saving, sending email, and validation. If the email sending logic changes, this class would have to be modified, which could break data saving.

Corrected version with SRP:

class UserRepository {    fun save(user: User) {        println("User saved to DB")    }}

class EmailService { fun send(user: User, message: String) { println("Email sent to ${user.email}") }}

class UserValidator { fun validate(user: User): Boolean { return user.name.isNotEmpty() }}

Now each class has a single responsibility, which simplifies testing and code modification.



2. Open/Closed Principle (OCP)

Theory: Software entities (classes, modules, functions) should be open for extension but closed for modification. This means you should be able to add new behavior without changing existing code.

Practice in Kotlin: Use abstractions (interfaces or abstract classes) and polymorphism. In Kotlin, sealed class and when expressions are great for this.

Example of violation:

class AreaCalculator {    fun calculate(shape: Any): Double {        return when (shape) {            is Circle -> Math.PI * shape.radius * shape.radius            is Square -> shape.side * shape.side            else -> throw IllegalArgumentException("Unknown shape")        }    }}

To add a new shape (e.g., Triangle), you would have to modify the calculate method.

Corrected version with OCP:

interface Shape {    fun area(): Double}

class Circle(private val radius: Double) : Shape { override fun area() = Math.PI * radius * radius}

class Square(private val side: Double) : Shape { override fun area() = side * side}

class Triangle(private val base: Double, private val height: Double) : Shape { override fun area() = 0.5 * base * height}

class AreaCalculator { fun calculate(shape: Shape) = shape.area()}

Now, to add a new shape, you simply create a new class that implements the Shape interface.

Blogs

Book Recommendations