Паттерны проектирования в Kotlin: примеры и практика

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в паттерны проектирования в Kotlin

Паттерны проектирования (design patterns) — это проверенные решения типовых задач разработки. В языке Kotlin многие классические паттерны реализуются более лаконично и безопасно благодаря встроенным возможностям: null-безопасности, функциям-расширениям, корутинам и мощной системе типов.

В этой статье мы разберём три группы паттернов: порождающие, структурные и поведенческие — с реальными примерами на Kotlin. Вы узнаете, как использовать их в повседневной разработке для создания гибкой и поддерживаемой архитектуры.

Порождающие паттерны (Creational)

1. Singleton (Одиночка)

В Kotlin одиночка реализуется ключевым словом object. Это потокобезопасный ленивый синглтон без лишнего кода.

object DatabaseConnection {
    private val url = "jdbc:postgresql://localhost:5432/mydb"
    private val user = "admin"
    private val password = "secret"

    fun connect(): String {
        return "Connected to $url as $user"
    }
}

fun main() {
    println(DatabaseConnection.connect())
    // Проверяем, что это один и тот же объект
    val ref1 = DatabaseConnection
    val ref2 = DatabaseConnection
    println(ref1 === ref2) // true
}

Преимущество: не нужно писать synchronized или volatile — Kotlin гарантирует единственный экземпляр.

2. Factory Method (Фабричный метод)

Позволяет делегировать создание объектов подклассам. В Kotlin удобно использовать sealed class и companion object.

sealed class Notification {
    data class Email(val to: String, val subject: String) : Notification()
    data class SMS(val phone: String, val message: String) : Notification()
    data class Push(val deviceId: String, val title: String) : Notification()

    companion object Factory {
        fun createEmail(to: String, subject: String): Notification = Email(to, subject)
        fun createSMS(phone: String, message: String): Notification = SMS(phone, message)
        fun createPush(deviceId: String, title: String): Notification = Push(deviceId, title)
    }
}

fun send(notification: Notification) {
    when (notification) {
        is Notification.Email -> println("Email to ${notification.to}")
        is Notification.SMS -> println("SMS to ${notification.phone}")
        is Notification.Push -> println("Push to ${notification.deviceId}")
    }
}

fun main() {
    val email = Notification.createEmail("user@example.com", "Hello")
    send(email)
}

3. Builder (Строитель)

В Kotlin Builder можно заменить именованными аргументами и значениями по умолчанию. Но для сложных объектов с валидацией классический Builder остаётся актуальным.

class Pizza private constructor(
    val size: String,
    val cheese: Boolean,
    val pepperoni: Boolean,
    val mushrooms: Boolean
) {
    class Builder {
        private var size: String = "Medium"
        private var cheese: Boolean = false
        private var pepperoni: Boolean = false
        private var mushrooms: Boolean = false

        fun size(size: String) = apply { this.size = size }
        fun cheese() = apply { this.cheese = true }
        fun pepperoni() = apply { this.pepperoni = true }
        fun mushrooms() = apply { this.mushrooms = true }

        fun build(): Pizza {
            require(size in listOf("Small", "Medium", "Large")) { "Invalid size" }
            return Pizza(size, cheese, pepperoni, mushrooms)
        }
    }
}

fun main() {
    val pizza = Pizza.Builder()
        .size("Large")
        .cheese()
        .pepperoni()
        .build()
    println("Pizza: ${pizza.size}, cheese=${pizza.cheese}, pepperoni=${pizza.pepperoni}")
}

Структурные паттерны (Structural)

4. Adapter (Адаптер)

Адаптер позволяет объектам с несовместимыми интерфейсами работать вместе. В Kotlin удобно использовать функции-расширения.

// Старый интерфейс
interface OldLogger {
    fun logMessage(msg: String)
}

// Новый интерфейс
interface NewLogger {
    fun info(tag: String, message: String)
}

// Адаптер через extension function
fun OldLogger.toNewLogger(): NewLogger {
    return object : NewLogger {
        override fun info(tag: String, message: String) {
            this@toNewLogger.logMessage("[$tag] $message")
        }
    }
}

class ConsoleOldLogger : OldLogger {
    override fun logMessage(msg: String) = println(msg)
}

fun main() {
    val oldLogger = ConsoleOldLogger()
    val newLogger = oldLogger.toNewLogger()
    newLogger.info("APP", "Hello from Adapter!")
}

5. Decorator (Декоратор)

Динамически добавляет объекту новые обязанности. В Kotlin можно использовать делегирование через by.

interface Coffee {
    fun cost(): Double
    fun description(): String
}

class SimpleCoffee : Coffee {
    override fun cost() = 2.0
    override fun description() = "Simple coffee"
}

class MilkDecorator(private val coffee: Coffee) : Coffee by coffee {
    override fun cost() = coffee.cost() + 0.5
    override fun description() = coffee.description() + " + milk"
}

class SugarDecorator(private val coffee: Coffee) : Coffee by coffee {
    override fun cost() = coffee.cost() + 0.3
    override fun description() = coffee.description() + " + sugar"
}

fun main() {
    val coffee = SugarDecorator(MilkDecorator(SimpleCoffee()))
    println("${coffee.description()} — $${coffee.cost()}")
}

Поведенческие паттерны (Behavioral)

6. Observer (Наблюдатель)

Паттерн "издатель-подписчик". В Kotlin есть встроенная поддержка через MutableStateFlow из kotlinx.coroutines.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow

class NewsPublisher {
    private val _news = MutableStateFlow("No news yet")
    val news = _news.asStateFlow()

    fun publish(news: String) {
        _news.value = news
    }
}

fun main() = runBlocking {
    val publisher = NewsPublisher()

    // Подписчик 1
    val job1 = launch {
        publisher.news.collect { value ->
            println("Subscriber 1 received: $value")
        }
    }

    // Подписчик 2
    val job2 = launch {
        publisher.news.collect { value ->
            println("Subscriber 2 received: $value")
        }
    }

    publisher.publish("Kotlin 2.0 released!")
    delay(100)
    publisher.publish("New coroutines feature")

    delay(200)
    job1.cancel()
    job2.cancel()
}

7. Strategy (Стратегия)

Позволяет менять алгоритм на лету. В Kotlin стратегии удобно передавать как лямбды.

Блоги

Рекомендаций по книгам