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

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

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

Начать курс

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

Паттерны проектирования (design patterns) — это проверенные решения типовых задач разработки. В Kotlin, благодаря его лаконичному синтаксису и мощным возможностям (data class, object, extension functions, корутины), многие классические паттерны реализуются проще и элегантнее, чем в Java. В этой статье мы разберем ключевые паттерны на Kotlin от порождающих до поведенческих, с реальными примерами кода.

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

Эти паттерны управляют процессом создания объектов, делая систему независимой от способа их создания.

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

Гарантирует, что у класса есть только один экземпляр. В Kotlin для этого используется ключевое слово object.

object DatabaseConnection {
    val connectionString: String = "jdbc:postgresql://localhost:5432/mydb"

    fun connect() {
        println("Connecting to $connectionString")
    }
}

fun main() {
    DatabaseConnection.connect()
    println(DatabaseConnection.connectionString)
}

Когда использовать: для менеджеров конфигурации, пулов потоков, кэшей.

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

Определяет интерфейс для создания объекта, но позволяет подклассам изменять тип создаваемого объекта.

// Абстрактный продукт
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")
}

// Фабрика
abstract 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!")
}

Когда использовать: когда заранее неизвестен тип создаваемых объектов.

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

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

data class Pizza(
    val size: String,
    val cheese: Boolean = false,
    val pepperoni: Boolean = false,
    val mushrooms: Boolean = false
) {
    // Встроенный 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)
}

Когда использовать: при большом количестве опциональных параметров.

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

Эти паттерны определяют способы построения классов и объектов в более крупные структуры.

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

Позволяет объектам с несовместимыми интерфейсами работать вместе. В Kotlin часто используется через extension functions.

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

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

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

fun main() {
    val oldLogger = object : OldLogger {
        override fun logMessage(msg: String) = println(msg)
    }

    val newLogger = oldLogger.adaptToNew()
    newLogger.log("INFO", "System started")
}

Когда использовать: при интеграции со сторонними библиотеками или legacy-кодом.

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

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

interface Coffee {
    val cost: Double
    val description: String
}

class SimpleCoffee : Coffee {
    override val cost: Double = 5.0
    override val description: String = "Black coffee"
}

class MilkDecorator(private val coffee: Coffee) : Coffee {
    override val cost: Double = coffee.cost + 1.5
    override val description: String = coffee.description + ", milk"
}

class SugarDecorator(private val coffee: Coffee) : Coffee {
    override val cost: Double = coffee.cost + 0.5
    override val description: String = coffee.description + ", sugar"
}

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

Когда использовать: когда нужно добавлять функциональность динамически, избегая наследования.

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

Эти паттерны определяют алгоритмы и распределение обязанностей между объектами.

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

Определяет зависимость "один ко многим" между объектами. В Kotlin можно использовать Delegates.observable.

import kotlin.properties.Delegates

class WeatherStation {
    var temperature: Int by Delegates.observable(20) { _, old, new ->
        println("Temperature changed from $old to $new")
    }
}

fun main() {
    val station = WeatherStation()
    station.temperature = 25  // вызовет лямбду
    station.temperature = 30
}

Для сложных сценариев используйте MutableLiveData или Flow из корутин.

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

Определяет семейство алгоритмов и делает их взаимозаменяемыми.

interface PaymentStrategy {
    fun pay(amount: Double)
}

class CreditCardPayment(private val cardNumber: String) : PaymentStrategy {
    override fun pay(amount: Double) {
        println("Paid $$amount via credit card $cardNumber")
    }
}

class PayPalPayment(private val email: String) : PaymentStrategy {
    override fun pay(amount: Double) {
        println("Paid $$amount via PayPal ($email)")
    }
}

class ShoppingCart {
    private var strategy: PaymentStrategy? = null

    fun setPaymentStrategy(strategy: PaymentStrategy) {
        this.strategy                

Блоги

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