Шаг 219.
Основы Kotlin.
Расширения. Анонимные функции с приемниками

    На этом шаге мы рассмотрим особенности использования таких функций.

    Существует возможность использовать анонимные функции с приемниками, обладающие мощным эффектом. Чтобы понять, что имеется в виду под "анонимными функциями с приемниками", посмотрите на объявление функции apply(), которая встречалась вам на 99 шаге:

public inline fun <T> T.apply(block: T.() -> Unit): T {
   block()
   return this
}

    Напомним, что apply() позволила вам установить свойства конкретного экземпляра объекта-приемника внутри лямбды, которая передается в аргументе. Например:

val menuFile = File("menu-file.txt").apply { 
  setReadable(true) 
  setWritable(true) 
  setExecutable(false)
}

    Это позволило избежать явного вызова каждой функции для переменной menuFile. Вместо этого вы вызвали их неявно, внутри лямбды. Волшебство apply() обеспечивается объявлением анонимной функции с объектом-приемником.

    Еще раз взгляните на объявление apply() и обратите внимание, как объявлен параметр-функция с именем block():

public inline fun <T> T.apply(block: T.() -> Unit): T { 
  block() 
  return this
}

    Параметр-функция block() является не только лямбдой, но и расширением обобщенного типа T: T.() -> Unit. Именно это позволяет лямбде иметь неявный доступ к функциям и свойствам экземпляра приемника.

    Приемник в лямбде, указанный в расширении, одновременно является экземпляром, для которого вызывается apply(), что открывает доступ к функциям и свойствам приемника в теле лямбды.

    Подобный стиль позволяет создавать так называемый предметно-ориентированный язык, который можно назвать API-подобным. Он использует функции приемника, настроенные с использованием лямбда-выражений, объявленных для доступа к ним. Например, фреймворк Exposed от JetBrains (github.com/JetBrains/Exposed) широко использует стиль предметно-ориентированного языка для определения своего API, который помогает объявлять SQL-запросы.

    Вы можете добавить в NyetHack функцию, которая будет использовать такой же стиль для настройки смертельно опасного гоблина в комнате.


Просто добавьте следующий код в свой проект NyetHack в качестве эксперимента.
fun Room.configurePitGoblin(block: Room.(Goblin) -> Goblin): Room {
  val goblin = block(Goblin("Pit Goblin", description = "An Evil Pit
    Goblin"))
  monster = goblin 
  return this
}

    Это расширение для Room принимает лямбду, которая получает Room в роли приемника. В результате свойства Room будут доступны внутри вашей лямбды, поэтому гоблина можно настроить, используя свойства приемника Room:

  currentRoom.configurePitGoblin { goblin -> 
    goblin.healthPoints = dangerLevel * 3 
    goblin
  }


Обратите внимание, что необходимо изменить уровень видимости dangerLevel для Room, чтобы на самом деле получить доступ к свойству dangerLevel.

    На следующем шаге мы рассмотрим несколько заданий для самостоятельного решения.




Предыдущий шаг Содержание Следующий шаг