Паттерны проектирования в 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