На этом шаге мы закончим рефакторинг приложения.
Переключим внимание на Player.
В Player.kt обратите внимание, что функции, ранее объявленные в Game.kt и перемещенные в Player, - auraColor() и formatHeathStatus() - теперь извлекают необходимые значения из свойств Player: в isBlessed, heathPoints и isImmortal. Когда функции были объявлены в Game.kt, они находились вне области видимости класса Player. Но теперь они стали функциями класса Player и автоматически получили доступ ко всем свойствам, объявленным в Player.
Это означает, что функции класса в Player больше не нуждаются в параметрах, так как все данные доступны внутри класса Player.
Измените заголовки функций, убрав из них параметры.
class Player { var name = "madrigal" get() = field.capitalize() private set(value) { field = value.trim() } var healthPoints = 89 val isBlessed = true private val isImmortal = false private fun auraColor(): String { val auraVisible = isBlessed && healthPoints > 50 || isImmortal val auraColor = if (auraVisible) "GREEN" else "NONE" return auraColor } private fun formatHealthStatus() = when (healthPoints) { 100 -> "is in excellent condition!" in 90..99 -> "has a few scratches." in 75..89 -> if (isBlessed) { "has some minor wounds but is healing quite quickly!" } else { "has some minor wounds." } in 15..74 -> "looks pretty hurt." else -> "is in awful condition!" } fun castFireball(numFireballs: Int = 2) = println("A glass of Fireball springs into existence. (x$numFireballs)") }
Рис.1. Удаление ненужных параметров из функций класса (Player.kt)
До этого изменения ссылка на heathPoints внутри функции formatHealthStatus() была бы ссылкой на параметр функции formatHealthStatus(), потому что эта ссылка была ограничена областью видимости функции. Без переменной с именем heathPoints в области видимости функции самая близкая область видимости находится на уровне класса, в котором объявлено свойство heathPoints.
Далее, обратите внимание, что две функции класса объявлены как приватные. Это не было проблемой, когда они находились в том же файле, откуда вызывались. Но теперь они стали приватными в классе Player и оказались недоступными для других классов. Доступ к этим функциям следует открыть, поэтому удалите ключевое слово private из объявлений функций auraColor() и formatHeathStatus().
class Player { var name = "madrigal" get() = field.capitalize() private set(value) { field = value.trim() } var healthPoints = 89 val isBlessed = true private val isImmortal = false fun auraColor(): String { val auraVisible = isBlessed && healthPoints > 50 || isImmortal val auraColor = if (auraVisible) "GREEN" else "NONE" return auraColor } fun formatHealthStatus() = when (healthPoints) { 100 -> "is in excellent condition!" in 90..99 -> "has a few scratches." in 75..89 -> if (isBlessed) { "has some minor wounds but is healing quite quickly!" } else { "has some minor wounds." } in 15..74 -> "looks pretty hurt." else -> "is in awful condition!" } fun castFireball(numFireballs: Int = 2) = println("A glass of Fireball springs into existence. (x$numFireballs)") }
Рис.2. Превращение функции класса в public (Player.kt)
Теперь все свойства и функции объявлены в правильных местах, но для их вызова в Game.kt используется неверный синтаксис по трем причинам:
Измените функцию printPlayerStatus(), чтобы она принимала экземпляр Player как аргумент и могла обращаться к необходимым свойствам и вызывать новые версии auraColor() и formatHeathStatus() без параметров.
fun main() { val player = Player() player.castFireball() // Аура val auraColor = auraColor() // Состояние игрока printPlayerStatus(player) } fun printPlayerStatus(player: Player) { println("(Aura: ${player.auraColor()}) " + "(Blessed: ${if (player.isBlessed) "YES" else "NO"})") println("${player.name} ${player.healthStatus()}") }
Рис.3. Вызов функций класса (Game.kt)
Это изменение в заголовке printPlayerStatus() помогает сохранить его чистоту от лишних деталей реализации Player. Сравните эти две сигнатуры:
printPlayerStatus(player: Player) printPlayerStatus(auraColor: String, isBlessed: Boolean, name: String, healthStatus: String)
Какая выглядит опрятнее? Вторая сигнатура требует от вызывающего знать довольно много о реализации Player. Первая требует лишь экземпляр Player. В этом примере вы видите одно из преимуществ объектно-ориентированного программирования: так как данные теперь являются частью класса Player, на них можно ссылаться без необходимости передавать их в явной форме.
Давайте остановимся и посмотрим, чего добились с помощью этого рефакторинга. Класс Player теперь содержит все данные и поведение конкретного объекта "игрок". Он открывает доступ к трем свойствам и трем функциям и скрывает детали реализации в приватных элементах, которые должны быть доступны только классу Player. Эти функции объявляют возможности игрока: игрок может сообщить о своем состоянии здоровья, цвете ауры и т. д.
По мере развития приложения важно поддерживать управляемость области видимости. Приобщаясь к объектно-ориентированному программированию, вы присоединяетесь к идее, что каждый объект должен выполнять свои собственные обязанности и открывать доступ только к тем функциям и свойствам, которые необходимы для работы других функций и классов. Теперь Player полностью отражает, что значит быть игроком в NyetHack, а Game.kt содержит цикл игры в виде более простой и понятной функции main().
Запустите Game.kt, чтобы убедиться, что все работает, как и раньше. В следующих шагах мы построим фундамент для игры NyetHack, сделаем ее более сложной и добавим возможности, опирающиеся на парадигму объектно-ориентированного программирования.
Вы узнаете больше способов создания экземпляра Player, познакомившись с дополнительными приемами инициализации. Но прежде чем развивать приложение дальше, надо уделить внимание пакетам.
На следующем шаге мы рассмотрим использование пакетов.