SOLID Principles in Kotlin: Advanced Application and Practice

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

SOLID Principles in Kotlin: Theory and Practice at an Advanced Level



SOLID is a fundamental set of object-oriented design principles introduced by Robert Martin. In the context of Kotlin, which combines the best features of OOP and functional programming, these principles gain new implementation possibilities. In this article, we will examine each principle at an advanced level, using unique language features: inline functions, delegation, sealed classes, and extension functions.



1. Single Responsibility Principle (SRP) and Delegation



The Single Responsibility Principle states: a class should have only one reason to change. In Kotlin, the Delegation pattern is often used to comply with SRP. The by keyword allows automatically delegating interface implementation to another object.



Problem: The UserService class is responsible for business logic, logging, and sending emails.



// ❌ SRP Violationclass UserService {    fun registerUser(email: String, password: String) {        // Business logic        println("User registered")        // Logging        log("User registered: $email")        // Sending email        sendEmail(email, "Welcome!")    }

private fun log(message: String) { /* ... */ } private fun sendEmail(to: String, body: String) { /* ... */ }}


Solution using delegation:



// ✅ SRP Compliance via Delegationinterface Logger {    fun log(message: String)}

interface EmailSender { fun send(to: String, body: String)}

class ConsoleLogger : Logger { override fun log(message: String) = println("LOG: $message")}

class SmtpEmailSender : EmailSender { override fun send(to: String, body: String) = println("Sending email to $to: $body")}

class UserService( private val logger: Logger, private val emailSender: EmailSender) : Logger by logger, EmailSender by emailSender { // 👈 Delegation

fun registerUser(email: String, password: String) { println("User registered") log("User registered: $email") send(email, "Welcome!") }}

// Usagefun main() { val service = UserService(ConsoleLogger(), SmtpEmailSender()) service.registerUser("user@example.com", "pass123")}


Using by avoids boilerplate code and clearly separates responsibilities. The UserService class now only handles registration, while logging and sending are delegated to separate classes.



2. Open-Closed Principle (OCP) and Sealed Classes



The Open-Closed Principle: classes should be open for extension but closed for modification. In Kotlin, sealed classes and extension functions are ideal for this.



Problem: The processPayment function uses when with type checking, and adding a new payment method requires modifying it.



// ❌ OCP Violationfun processPayment(type: String, amount: Double) {    when (type) {        "card" -> println("Processing card payment: $amount")        "paypal" -> println("Processing PayPal payment: $amount")        else -> throw IllegalArgumentException("Unknown payment type")    }}


Solution with sealed classes:



// ✅ OCP Compliancesealed class PaymentMethod {    data class Card(val number: String, val cvv: String) : PaymentMethod()    data class PayPal(val email: String) : PaymentMethod()    object Cash : PaymentMethod()}

// Function is closed for modificationfun processPayment(method: PaymentMethod, amount: Double) { // When new subclasses appear, the compiler will require handling them

Blogs

Book Recommendations