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

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

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

Начать курс

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

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

1. Паттерн Компоновщик (Composite)

Паттерн Компоновщик позволяет клиентам единообразно работать с отдельными объектами и их группами. Это идеальное решение для построения иерархических структур, таких как файловые системы, меню интерфейсов или графические сцены.

В Kotlin мы часто используем sealed классы для представления узлов дерева. Рассмотрим пример построения структуры "Файлы и Папки":

sealed class FileSystemNode(open val name: String) {
    data class File(override val name: String, val size: Long) : FileSystemNode(name)
    data class Directory(
        override val name: String,
        val children: MutableList<FileSystemNode> = mutableListOf()
    ) : FileSystemNode(name) {
        fun add(node: FileSystemNode) {
            children.add(node)
        }
        fun remove(node: FileSystemNode) {
            children.remove(node)
        }
        fun getTotalSize(): Long = children.sumOf {
            when (it) {
                is File -> it.size
                is Directory -> it.getTotalSize()
            }
        }
    }
}

// Использование
fun main() {
    val root = FileSystemNode.Directory("root")
    root.add(FileSystemNode.File("readme.txt", 1024))
    val subDir = FileSystemNode.Directory("src")
    subDir.add(FileSystemNode.File("main.kt", 4096))
    root.add(subDir)
    println("Общий размер: ${root.getTotalSize()} байт") // 5120
}

Плюсы: упрощает клиентский код, поддерживает рекурсивные операции. Минусы: может усложнить систему, если дерево слишком глубокое.

2. Паттерн Посетитель (Visitor)

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

В Kotlin отлично работает с sealed классами. Реализуем пример вычисления площади для фигур:

sealed class Shape
class Circle(val radius: Double) : Shape()
class Rectangle(val width: Double, val height: Double) : Shape()

interface ShapeVisitor<T> {
    fun visit(circle: Circle): T
    fun visit(rectangle: Rectangle): T
}

class AreaCalculator : ShapeVisitor<Double> {
    override fun visit(circle: Circle) = Math.PI * circle.radius * circle.radius
    override fun visit(rectangle: Rectangle) = rectangle.width * rectangle.height
}

// Использование
fun <T> Shape.accept(visitor: ShapeVisitor<T>): T = when (this) {
    is Circle -> visitor.visit(this)
    is Rectangle -> visitor.visit(this)
}

fun main() {
    val shapes = listOf(Circle(5.0), Rectangle(3.0, 4.0))
    val calculator = AreaCalculator()
    shapes.forEach { println("Площадь: ${it.accept(calculator)}") }
}

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

3. Паттерн Состояние (State)

Паттерн Состояние позволяет объекту менять своё поведение в зависимости от внутреннего состояния. Это элегантная альтернатива громоздким if-else или switch-case.

В Kotlin мы можем использовать sealed классы для представления состояний. Пример — заказ в интернет-магазине:

sealed class OrderState {
    object New : OrderState()
    object Paid : OrderState()
    object Shipped : OrderState()
    object Delivered : OrderState()
}

class Order {
    var state: OrderState = OrderState.New
        private set

    fun pay() {
        state = when (state) {
            is OrderState.New -> {
                println("Оплата прошла успешно")
                OrderState.Paid
            }
            else -> {
                println("Невозможно оплатить: текущее состояние $state")
                state
            }
        }
    }

    fun ship() {
        state = when (state) {
            is OrderState.Paid -> {
                println("Заказ отправлен")
                OrderState.Shipped
            }
            else -> {
                println("Невозможно отправить: текущее состояние $state")
                state
            }
        }
    }

    fun deliver() {
        state = when (state) {
            is OrderState.Shipped -> {
                println("Заказ доставлен")
                OrderState.Delivered
            }
            else -> {
                println("Невозможно подтвердить доставку: текущее состояние $state")
                state
            }
        }
    }
}

fun main() {
    val order = Order()
    order.pay()   // Оплата прошла успешно
    order.ship()  // Заказ отправлен
    order.deliver() // Заказ доставлен
    order.pay()   // Невозможно оплатить: текущее состояние Delivered
}

Преимущество: код становится читаемым и легко расширяемым — добавление нового состояния не требует изменения существующих методов.

4. Паттерн Мост (Bridge)

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

Рассмотрим пример: система уведомлений с разными платформами и типами сообщений.

// Реализация (Implementation)
interface MessageSender {
    fun send(message: String)
}

class EmailSender : MessageSender {
    override fun send(message: String) = println("Email: $message")
}

class SmsSender : MessageSender {
    override fun send(message: String) = println("SMS: $message")
}

// Абстракция (Abstraction)
abstract class Notification(protected val sender: MessageSender) {
    abstract fun notifyUser(message: String)
}

class UrgentNotification(sender: MessageSender) : Notification(sender) {
    override fun notifyUser(message: String) {
        sender.send("[СРОЧНО] $message")
    }
}

class RegularNotification(sender: MessageSender) : Notification(sender) {
    override fun notifyUser(message: String) {
        sender.send("[ОБЫЧНОЕ] $message")
    }
}

// Использование
fun main() {
    val emailUrgent = UrgentNotification(EmailSender())
    val smsRegular = RegularNotification(SmsSender())
    emailUrgent.notifyUser("                

Блоги

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