Делегирование с помощью by
Часть раздела Объектно-ориентированное программирование путешествия по Kotlin на Coddy. Урок 46 из 57.
Делегирование означает, что объект передаёт работу другому объекту вместо наследования. Kotlin поддерживает это напрямую: class Polite(private val inner: Greeter) : Greeter by inner реализует Greeter, перенаправляя каждый вызов к inner, а класс переопределяет только то, что он изменяет:
interface Greeter {
fun hello(name: String): String
fun bye(name: String): String
}
class English : Greeter {
override fun hello(name: String) = "Hello, $name"
override fun bye(name: String) = "Bye, $name"
}
class Polite(private val inner: Greeter) : Greeter by inner {
override fun hello(name: String) = inner.hello(name) + ", pleased to meet you"
}Внутри main:
val g = Polite(English())
println(g.hello("Ada"))
println(g.bye("Ada"))Вывод:
Hello, Ada, pleased to meet you
Bye, AdaДелегирование — это альтернатива наследованию. Оно работает с интерфейсами, реализации которых являются финальными, а делегатом может быть любой объект, выбранный во время выполнения программы. Это множество подсчитывает каждый вызов add и передаёт всё остальное обычному MutableSet:
class CountingSet<T>(private val inner: MutableSet<T> = mutableSetOf()) : MutableSet<T> by inner {
var attempts = 0
private set
override fun add(element: T): Boolean {
attempts++
return inner.add(element)
}
}Внутри main:
val tags = CountingSet<String>()
tags.add("kotlin")
tags.add("jvm")
tags.add("kotlin")
println("${tags.size} tags after ${tags.attempts} adds, contains jvm: ${"jvm" in tags}")Результат:
2 tags after 3 adds, contains jvm: trueДелегированные свойства передают геттер и сеттер свойства другому объекту — снова с помощью by. lazy из главы 2 — один из таких делегатов, а ещё одним может быть map: каждое свойство считывает элемент с собственным именем:
class User(values: Map<String, Any?>) {
val name: String by values
val age: Int by values
}Внутри main:
val u = User(mapOf("name" to "Ada", "age" to 36))
println("${u.name} is ${u.age}")Вывод:
Ada is 36Вы можете написать собственный делегат свойства: класс с operator fun getValue, а также с setValue для var. Kotlin вызывает их при каждом чтении и записи свойства. Этот делегат обрезает каждый присвоенный текст и подсчитывает изменения:
import kotlin.reflect.KProperty
class Trimmed {
private var value = ""
var changes = 0
operator fun getValue(thisRef: Any?, property: KProperty<*>) = value
operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: String) {
value = newValue.trim()
changes++
}
}
class Form {
val nameField = Trimmed()
var name: String by nameField
}Внутри main:
val form = Form()
form.name = " Ada "
form.name = "Ada L. "
println("[${form.name}] after ${form.nameField.changes} changes")Вывод:
[Ada L.] after 2 changesЗадание
ЛегкоИнтерфейс Storage (с put, get и keys) и его реализация MapStorage предоставлены. Напишите два декоратора с делегированием классов, каждый из которых переопределяет только put: LoggingStorage(inner) записывает put a=1 в свой доступный только для чтения список log и передаёт вызов дальше, а LimitedStorage(inner, max) отказывается (возвращает false) добавлять новый ключ, когда уже существует max ключей, при этом обновление существующих ключей по-прежнему разрешено. Всё остальное передаётся дальше с помощью by.
Предоставленный код оборачивает MapStorage в LoggingStorage, а если первая строка содержит положительный лимит — также в LimitedStorage. Он выполняет команды put a 1, get a и keys, выводя ok, refused, значение или missing, а также список ключей, и в конце — log: put a=1, ....
Ваш код должен находиться в файлах LoggingStorage.kt, LimitedStorage.kt, Storage.kt и MapStorage.kt. В Main.kt находится предоставленный код ввода/вывода, и его нельзя изменять.
Попробуйте сами
fun main() {
// Предоставленный код ввода/вывода: оставьте его как есть
val input = generateSequence(::readLine).toList()
val base = MapStorage()
val logging = LoggingStorage(base)
val limit = input[0].toInt()
val storage: Storage = if (limit > 0) LimitedStorage(logging, limit) else logging
for (cmd in input.drop(1)) {
val p = cmd.split(" ")
when (p[0]) {
"put" -> println(if (storage.put(p[1], p[2])) "ok" else "refused")
"get" -> println(storage.get(p[1]) ?: "missing")
else -> println(storage.keys())
}
}
println("log: " + logging.log.joinToString(", ").ifEmpty { "empty" })
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Классы и объекты
Работа с файламиОпределение классовПервичные конструкторыБлоки init и валидацияВторичные конструкторыПовторение — читательский билет4Наследование
Открытые классы и подклассыПереопределение членовsuper и конструкторыАбстрактные классыПовторение — каталог сотрудников7Специальные классы
Классы данныхКлассы перечисленийЗапечатанные иерархииequals, hashCode, toStringПовторение — трекер заказов10Паттерны проектирования. Часть 2
Делегирование с помощью byПаттерн декораторПаттерн строительПаттерн состояние2Свойства и инкапсуляция
Пользовательские аксессорыМодификаторы видимостиПриватные сеттерыlazy и lateinitПовторение — банковский счёт5Интерфейсы
Объявление интерфейсовМетоды по умолчаниюМножественные интерфейсыИнтерфейсы как типыПовторение — способы оплаты8Обобщения
Обобщённые функцииОбобщённые классыОграничения типовВариативность с in и outПовторение — очередь с приоритетом11Проект: Управление библиотекой
Книги и читателиВыдача книг3Объекты и компаньоны
Объявления объектовОбъекты-компаньоныКонстанты с constОбъектные выраженияПовторение — билетная касса6Полиморфизм
Динамическая диспетчеризацияПроверка типов и умные приведения типовwhen с типамиФункции-расширенияПовторение — смотритель зоопарка9Паттерны проектирования. Часть 1
Паттерн «Одиночка»Фабричный паттернПаттерн «Наблюдатель»Паттерн «Стратегия»Потренируйтесь самостоятельно: Песочница Kotlin