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

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

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

Начать курс

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

Паттерны проектирования — это проверенные решения типовых задач при разработке программного обеспечения. Они помогают создавать гибкую, масштабируемую и поддерживаемую архитектуру. В языке Kotlin, благодаря его лаконичному синтаксису и функциональным возможностям, многие классические паттерны реализуются особенно элегантно. В этой статье мы разберём три ключевых паттерна: Singleton, Factory Method и Observer, и покажем их реализацию на Kotlin с практическими примерами.

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

Паттерн Singleton гарантирует, что у класса существует только один экземпляр, и предоставляет глобальную точку доступа к нему. В Kotlin для этого есть встроенная конструкция — object. Она создаёт потокобезопасный синглтон без лишнего кода.

Реализация в Kotlin

Самый простой способ объявить синглтон — использовать ключевое слово object:

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

    fun connect(): String {
        return "Подключение к $url от имени $username"
    }
}

fun main() {
    println(DatabaseConfig.connect())
    // Вывод: Подключение к jdbc:postgresql://localhost:5432/mydb от имени admin
}

Если нужен классический Singleton с приватным конструктором (например, для наследования от другого класса), используйте companion object:

class Logger private constructor() {
    fun log(message: String) {
        println("[LOG]: $message")
    }

    companion object {
        private val instance: Logger by lazy { Logger() }

        fun getInstance(): Logger {
            return instance
        }
    }
}

fun main() {
    val logger = Logger.getInstance()
    logger.log("Приложение запущено")
}

Преимущество подхода с by lazy — ленивая инициализация, которая выполняется только при первом обращении.

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

Паттерн Factory Method определяет интерфейс для создания объекта, но позволяет подклассам изменять тип создаваемого объекта. В Kotlin это часто реализуется через sealed class или интерфейсы.

Реализация в Kotlin

Предположим, мы разрабатываем систему уведомлений. У нас есть разные типы уведомлений: Email, SMS и Push. Создадим фабрику для их генерации.

// Интерфейс для всех уведомлений
interface Notification {
    fun send(message: String)
}

// Конкретные реализации
class EmailNotification : Notification {
    override fun send(message: String) {
        println("Отправка Email: $message")
    }
}

class SMSNotification : Notification {
    override fun send(message: String) {
        println("Отправка SMS: $message")
    }
}

class PushNotification : Notification {
    override fun send(message: String) {
        println("Отправка Push-уведомления: $message")
    }
}

// Фабрика
object NotificationFactory {
    fun createNotification(type: String): Notification {
        return when (type.lowercase()) {
            "email" -> EmailNotification()
            "sms" -> SMSNotification()
            "push" -> PushNotification()
            else -> throw IllegalArgumentException("Неизвестный тип уведомления: $type")
        }
    }
}

fun main() {
    val notification = NotificationFactory.createNotification("sms")
    notification.send("Ваш код подтверждения: 1234")
    // Вывод: Отправка SMS: Ваш код подтверждения: 1234
}

Если количество типов фиксировано, лучше использовать sealed class для большей безопасности:

sealed class NotificationType {
    object Email : NotificationType()
    object SMS : NotificationType()
    object Push : NotificationType()
}

fun createNotification(type: NotificationType): Notification = when (type) {
    NotificationType.Email -> EmailNotification()
    NotificationType.SMS -> SMSNotification()
    NotificationType.Push -> PushNotification()
}

Такой подход гарантирует, что все возможные варианты обработаны, и компилятор выдаст ошибку, если вы забудете добавить новый тип.

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

Паттерн Observer устанавливает зависимость "один ко многим" между объектами: когда один объект (субъект) изменяет своё состояние, все зависимые объекты (наблюдатели) автоматически уведомляются и обновляются. В Kotlin для этого удобно использовать корутины или стандартные интерфейсы.

Реализация в Kotlin

Рассмотрим пример системы мониторинга температуры. Когда температура превышает порог, все подписанные наблюдатели должны получить уведомление.

// Интерфейс наблюдателя
interface TemperatureObserver {
    fun update(temperature: Double)
}

// Субъект (наблюдаемый объект)
class TemperatureSensor {
    private val observers = mutableListOf()
    private var temperature: Double = 0.0

    fun addObserver(observer: TemperatureObserver) {
        observers.add(observer)
    }

    fun removeObserver(observer: TemperatureObserver) {
        observers.remove(observer)
    }

    fun setTemperature(newTemperature: Double) {
        temperature = newTemperature
        notifyObservers()
    }

    private fun notifyObservers() {
        observers.forEach { it.update(temperature) }
    }
}

// Конкретный наблюдатель — дисплей
class Display(private val name: String) : TemperatureObserver {
    override fun update(temperature: Double) {
        println("$name: Текущая температура — $temperature°C")
    }
}

// Конкретный наблюдатель — система оповещения
class AlertSystem(private val threshold: Double) : TemperatureObserver {
    override fun update(temperature: Double) {
        if (temperature > threshold) {
            println("⚠️ ВНИМАНИЕ! Температура $temperature°C превышает порог $threshold°C")
        }
    }
}

fun main() {
    val sensor = TemperatureSensor()
    val display1 = Display("Дисплей №1")
    val display2 = Display("Дисплей №2")
    val alert = AlertSystem(30.0)

    sensor.addObserver(display1)
    sensor.addObserver(display2)
    sensor.addObserver(alert)

    sensor.setTemperature(25.0)
    // Дисплей №1: Текущая температура — 25.0°C
    // Дисплей №2: Текущая температура — 25.0°C

    sensor.setTemperature(35.0)
    // Дисплей №1: Текущая температура — 35.0°C
    // Дисплей №2: Текущая температура — 35.0°C
    // ⚠️ ВНИМАНИЕ! Температура 35.0°C превышает порог 30.0°C
}

В Kotlin можно упростить реализацию, используя лямбды вместо отдельных классов наблюдателей:

class SimpleSensor {
    private val listeners = mutableListOf<(Double) -> Unit>()
    private var temperature: Double = 0.0

    fun onTemperatureChanged(listener: (Double) -> Unit) {
        listeners.add(listener)
    }

    fun setTemperature(newTemp: Double) {
        temperature = newTemp
        listeners.forEach { it(temperature) }
    }
}

fun main                

Блоги

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