SOLID Principles in Kotlin: Theory and Practice from A to Z

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 (Uncle Bob). In the Kotlin ecosystem, which combines the best features of object-oriented and functional programming, adhering to SOLID principles helps create flexible, testable, and maintainable code. These principles are especially relevant for Android development, server-side applications on Ktor, and multithreaded systems using coroutines.



In this article, we will break down each of the five SOLID principles with examples adapted to Kotlin idioms. You will learn how to avoid common mistakes and make your code not just functional, but truly high-quality.



1. Single Responsibility Principle (SRP)



Essence of the Principle

A class should have only one reason to change. In other words, each class is responsible for exactly one functionality.



Bad Example (SRP Violation)

class UserManager {    fun createUser(name: String, email: String) { /* ... */ }    fun deleteUser(id: Int) { /* ... */ }    fun sendEmail(to: String, subject: String) { /* ... */ }    fun generateReport(userId: Int): String { /* ... */ }}

Here, UserManager is responsible for user management, sending emails, and generating reports. Changing the email logic would require modifying this class, even though it should not be concerned with email.



Good Example (SRP Compliance)

class UserRepository {    fun createUser(name: String, email: String) { /* ... */ }    fun deleteUser(id: Int) { /* ... */ }}

class EmailService { fun sendEmail(to: String, subject: String) { /* ... */ }}

class ReportGenerator { fun generateReport(userId: Int): String { /* ... */ }}

Now each class has one reason to change. This simplifies testing and code maintenance.



2. Open-Closed Principle (OCP)



Essence of the Principle

Software entities (classes, modules, functions) should be open for extension but closed for modification. This means new behavior is added through inheritance or composition, not by modifying existing code.



Bad Example (OCP Violation)

class PaymentProcessor {    fun process(paymentType: String) {        when (paymentType) {            "credit_card" -> // process credit card            "paypal" -> // process PayPal            else -> throw IllegalArgumentException()        }    }}

When adding a new payment method (e.g., Apple Pay), you would have to modify PaymentProcessor, which violates OCP.



Good Example (OCP Compliance)

interface PaymentMethod {    fun process(amount: Double)}

class CreditCardPayment : PaymentMethod { override fun process(amount: Double) { /* ... */ }}

class PayPalPayment : PaymentMethod { override fun process(amount: Double) { /* ... */ }}

class PaymentProcessor(private val method: PaymentMethod) { fun processPayment(amount: Double) { method.process(amount) }}

Now, to add a new payment method, you simply create a class implementing PaymentMethod without touching existing code.



3. Liskov Substitution Principle (LSP)



Essence of the Principle

Objects in a program should be replaceable with instances of their subtypes without altering the correctness of the program. Simply put, if you have a class Bird and a subclass Penguin, code that works with Bird should not break when substituting Penguin.



Bad Example (LSP Violation)

open class Rectangle {    open var width: Int = 0                

Blogs

Book Recommendations