Design Patterns in Kotlin with Examples: From A to Z
Design patterns are proven solutions to common development problems. In Kotlin, thanks to its concise syntax and powerful features (data class, object, extension functions, coroutines), many classic patterns are implemented more simply and elegantly than in Java. In this article, we will cover key patterns in Kotlin, from creational to behavioral, with real code examples.
1. Creational Patterns
These patterns manage the object creation process, making the system independent of how objects are created.
1.1. Singleton
Ensures that a class has only one instance. In Kotlin, the object keyword is used for this.
object DatabaseConnection { val connectionString: String = "jdbc:postgresql://localhost:5432/mydb"
fun connect() { println("Connecting to $connectionString") }}
fun main() { DatabaseConnection.connect() println(DatabaseConnection.connectionString)}When to use: for configuration managers, thread pools, caches.
1.2. Factory Method
Defines an interface for creating an object, but lets subclasses alter the type of object that will be created.
// Abstract productinterface Notification { fun send(message: String)}
// Concrete productsclass EmailNotification : Notification { override fun send(message: String) = println("Email: $message")}
class SMSNotification : Notification { override fun send(message: String) = println("SMS: $message")}
// Factoryabstract class NotificationFactory { abstract fun createNotification(): Notification
fun notifyUser(message: String) { val notification = createNotification() notification.send(message) }}
class EmailFactory : NotificationFactory() { override fun createNotification(): Notification = EmailNotification()}
class SMSFactory : NotificationFactory() { override fun createNotification(): Notification = SMSNotification()}
fun main() { val factory: NotificationFactory = EmailFactory() factory.notifyUser("Hello from Kotlin!")}When to use: when the type of objects to be created is not known in advance.
1.3. Builder
Allows creating complex objects step by step. In Kotlin, it is well implemented using apply and named arguments.
data class Pizza( val size: String, val cheese: Boolean = false, val pepperoni: Boolean = false, val mushrooms: Boolean = false) { // Built-in Builder class Builder(private val size: String) { private var cheese: Boolean = false private var pepperoni: Boolean = false private var mushrooms: Boolean = false
fun cheese(value: Boolean) = apply { cheese = value } fun pepperoni(value: Boolean) = apply { pepperoni = value } fun mushrooms(value: Boolean) = apply { mushrooms = value }
fun build() = Pizza(size, cheese, pepperoni, mushrooms) }}
fun main() { val pizza = Pizza.Builder("Large") .cheese(true) .pepperoni(true) .build() println(pizza)}When to use: when there are many optional parameters.
2. Structural Patterns
These patterns define ways to compose classes and objects into larger structures.
2.1. Adapter
Allows objects with incompatible interfaces to work together. In Kotlin, it is often used via extension functions.
// Old interfaceinterface OldLogger { fun logMessage(msg: String)}
// New interfaceinterface NewLogger { fun log(level: Stri