TLDR: используем имя переменной при инициализации ее значения

Допустим, в тесте нужно создать некоторое количество однотипных объектов, у которых есть характерное отличие в одном строковом свойстве. Для примера возьмем класс Color.

data class Color(val value: String)

Создадим несколько экземпляров, дадим переменным осмысленные имена.

val red = Color("red")
val green = Color("green")
val blue = Color("blue")

Имена переменных дублируют параметр конструктора. Копипаста. Давайте ее изживём.

val red by color()
val green by color()
val blue by color()

println(red.value)   // "red"
println(green.value) // "green"
println(blue.value)  // "blue"

Разберемся, что тут происходит.

Ключевое слово by подсказывает - функция color() возвращает делегат - объект со специальным методом, которому адресуется чтение свойства или локальной переменной. Второй параметр метода - это описание свойства, откуда можно узнать имя переменной.

Устройство делегата тривиально - при первом обращении к переменной создаем наш объект, запоминаем его и возвращаем, при последующих вызовах возвращаем его же.

class ColorDelegate {
    
  private var color: Color? = null
  
  operator fun getValue(thisRef: Nothing?, property: kotlin.reflect.KProperty<*>): Color {
    val res: Color = color ?: Color(property.name)
    color = res
    return res
  }
}

fun color(): ColorDelegate {
  return ColorDelegate()
}

Функция color() решает только эстетическую задачу, вместо ее вызова можно явно создавать ColorDelegate.

Спасибо! )

UPD: Как и любую магию, описанный прием нужно применять с осторожностью. Если имя несет еще функции, кроме маркерной - не стоит. И почти наверняка не стоит его применять в продуктовом коде.

Комментарии (7)


  1. Pab10
    07.07.2022 14:42
    +5

    Давайте вместо простого и понятного вызова конструктора родим дополнительную абстракцию с генериками и поржем над нубасами, которые пытаются въехать в проект. То, что язык дает такие возможности это прекрасно, но не забывайте про бритву. Подберите более релевантный пример, пожалуйста :)


    1. moonster Автор
      07.07.2022 15:22
      +2

      Мой реальный кейс, где я впервые применил механизм, вам бы понравился.

      Был у меня тест с таким сценарием:

      1 - создаем N ссылок на файлы

      2 - раскладываем ссылки в специальную структуру

      3 - применяем команду к структуре

      4 - проверяем, как структура изменилась

      Свойства ссылки в рамках структуры зависит места, которое она занимает в структуре, но при этом сама ссылка никакой информации о своем месте не содержит (и не должна).

      Ссылок много. Ну и в целом тест оказался не очень простой - код занял полтора экрана.

      Для упрощения читаемости теста имена переменных ссылок на файлы я сделал говорящими. Но этого оказалось мало, т.к. при дебаге в дебрях тестируемого кода имена исходных переменных уже недоступны. Поэтому я продублировал имя переменной в поле ссылки вручную, а потом и придумал, как это делать автоматически.

      Но это очень специфический случай, вникать в него вряд ли интересно. Поэтому я привел вырожденный пример чтоб проиллюстрировать механизм. В каких реальных случаях его стоит применять - то личное дело каждого. )


  1. Beholder
    07.07.2022 16:05
    +2

    Но только упаси вас бог переименовать такую переменную при "незначительном" рефакторинге.

    Было:

    val red by color()

    Захотели ясности:

    val foregroundColor by color() // было "red"


    1. moonster Автор
      07.07.2022 16:14

      Справедливое замечание, добавил.


  1. Lewigh
    07.07.2022 19:50

    Думаю наиболее полезно было бы при работе с БД, где из имени свойства можно было бы по умолчанию выводить имя колонки


    1. moonster Автор
      07.07.2022 20:03

      Не соглашусь.

      Допустим, мы зачем-то решили сами написать компонент, который генерит SQL или мапит ResultSet.

      Тогда проще взять имена полей более традиционным способом, примерно так:

          data class SomeEntity(val prop1: String, val prop2: Int)
      
          val properties = SomeEntity::class.declaredMemberProperties
      
          val someEntity = SomeEntity(prop1 = "Hello", prop2 = 100500)
      
          properties.forEach { prop ->
              println(prop.name + " = " + prop.get(someEntity))
          }
          // ==>
          //prop1 = Hello
          //prop2 = 100500


  1. Razoomnick
    08.07.2022 02:03
    +2

    Ох, не дай бог в реальности такое встретить. Имя переменной влияет на поведение программы, это даже круче классики

    #define TRUE FALSE // счастливой отладки суки

    Но для ненормального программирования была бы хорошая статья, возможность языка интересная.