Don’t Reinvent the Wheel, Delegate It! — статья об использовании встроенных в язык Kotlin средств делегирования реализаций классов и свойств.
Один из ключевых постулатов современного ООП-программирования гласит: «Предпочитайте делегирование наследованию». Это значит, что вместо наследования от какого-либо класса лучше включить инстанс этого класса в другой класс и вызывать его методы при вызове одноименных методов этого класса:
1 2 3 4 5 6 7 8 9 10 11 |
class Class1() { fun method1() { ... } fun method1() { ... } } class Class2(firstClass: Class1) { private val class1 = firstClass fun method1() { firstClass.method1() } fun method2() { firstClass.method2() } } |
Зачем это нужно? Чтобы избежать проблем, когда методы класса-родителя вызывают друг друга. Если method1() вызывает method2(), то, переопределив второй метод, мы сломаем работу первого. Делегирование решает эту проблему, так как Class1 и Class2 остаются не связанными друг с другом.
С другой стороны, делегирование усложняет код, и поэтому в Kotlin есть специальное ключевое слово by, сильно упрощающее жизнь разработчику. Благодаря ему реализовать второй класс можно с помощью всего одной строки:
1 |
class Class2(firstClass: Class1) : Class1 by firstClass |
Это действительно все. Компилятор Kotlin автоматически преобразует эту строку в аналог реализации Class2 из первого примера.
Kotlin также позволяет делегировать реализацию свойств. В следующей записи используется стандартный делегат lazy, инициализирующий переменную при первом обращении к ней:
1 |
val orm by lazy { KotlinORM("main.db") } |
Еще более интересно работает делегат map, позволяющий магическим образом взять значения из хешмапа.
1 2 3 4 5 6 7 8 9 10 11 12 |
class User(val map: Map<String, Any?>) { val name: String by map val age: Int by map } val user = User(mapOf( "name" to "John Doe", "age" to 25 )) println(user.name) // "John Doe" println(user.age) // 25 |
Кроме lazy и map, в стандартную библиотеку Kotlin включены еще три стандартных делегата:
- nutNull — аналог ключевого слова lateinit с более широкими возможностями;
- observable — позволяет выполнить код в момент чтения или записи переменной;
- vetoable — похож на observable, но срабатывает перед записью нового значения и может запретить изменение.
Ну и конечно же, любой разработчик может создать собственный делегат. Это всего лишь класс c реализацией операторов getValue() и setValue():
1 2 3 4 5 6 7 8 9 |
class Delegate { operator fun getValue(thisRef: Any?, property: KProperty<*>): String { return "$thisRef, thank you for delegating '${property.name}' to me!" } operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { println("$value has been assigned to '${property.name}' in $thisRef.") } } |
Андрей Бреслав, ключевой разработчик Kotlin, не раз заявлял, что не стал бы реализовывать делегирование классов, будь у него возможность вернуться в прошлое. Эта функция оказалась хоть и полезной, но слишком комплексной в реализации и конфликтующей с новыми возможностями языка Java.
первый пример не рабочий, дублирование методов