Освой Kotlin играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
Добавление модификатора sealed к суперклассу ограничивает возможность создания подклассов. Все прямые подклассы должны быть вложены в суперкласс. Запечатанный класс не может иметь наследников, объявленных вне класса.
sealed class SealedClass {
class One(val value: Int) : SealedClass()
class Two(val x: Int, val y: Int) : SealedClass()
fun eval(e: SealedClass): Int =
when (e) {
is SealedClass.One -> e.value
is SealedClass.Two -> e.x + e.y
}
}
В методе eval() при использовании when не пришлось использовать ветку else, так как sealed позволяет указать все доступные варианты и значение по умолчанию не требуется.
Если позже вы добавите новый подкласс, то выражение when будет ругаться и вы быстро вспомните, что нужно добавить новый код в программу.
По умолчанию запечатанный класс открыт и модификатор open не требуется. Запечатанные классы немного напоминают enum.
Создадим запечатанный класс AcceptedCurrency и три подкласса на его основе. Обратите внимание, что сейчас Kotlin разрешает объявлять подклассы не внутри запечатанного класса, а на одном уровне (для сравнения смотри старые примеры выше).
package ru.alexanderklimov.sealed
sealed class AcceptedCurrency
class Rubel : AcceptedCurrency()
class Dollar : AcceptedCurrency()
class Tugrik : AcceptedCurrency()
В классе активности создадим список принимаемых валют для покупки котят и применим его к адаптеру выпадающего списка.
package ru.alexanderklimov.sealed
import android.os.Bundle
import android.widget.ArrayAdapter
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
private val currencies = listOf(Rubel(), Dollar(), Tugrik())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val adapter = ArrayAdapter<AcceptedCurrency>(this,
android.R.layout.simple_spinner_item,
currencies)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
currencySpinner.adapter = adapter
}
}
Если запустить пример прямо сейчас, то в выпадающем списке увидим служебную информацию о классах. Не совсем то, что мы хотели увидеть.
Внесём изменения в запечатанный класс, чтобы у него появилось новое свойство.
sealed class AcceptedCurrency {
val name: String
get() = when (this) {
is Rubel -> "Рубль"
is Dollar -> "Доллар"
is Tugrik -> "Тугрик"
}
}
Если вы пропустите какой-то подкласс в выражении when, то компилятор будет ругаться. Это удобно, когда вы будете вносить изменения в код.
Поменяем код для адаптера.
val adapter =
ArrayAdapter<String>(this,
android.R.layout.simple_spinner_item,
currencies.map { it.name })
Теперь названия выводятся нормально.
Установим зависимость валют от рубля. Создадим в запечатанном классе абстрактное свойство valueInRubels. После этого студия потребует дополнить код у всех подклассов.
package ru.alexanderklimov.sealed
sealed class AcceptedCurrency {
abstract val valueInRubels: Double
var amount: Double = 0.0
val name: String
get() = when (this) {
is Rubel -> "Рубль"
is Dollar -> "Доллар"
is Tugrik -> "Тугрик"
}
}
class Rubel : AcceptedCurrency() {
override val valueInRubels = 1.00
}
class Dollar : AcceptedCurrency() {
override val valueInRubels = 70.0
}
class Tugrik : AcceptedCurrency() {
override val valueInRubels = 5.0
}
Добавим в класс ещё одну переменную ammount и функцию для подсчёта общей суммы.
sealed class AcceptedCurrency {
abstract val valueInRubels: Double
var amount: Double = 0.0
val name: String
get() = when (this) {
is Rubel -> "Рубль"
is Dollar -> "Доллар"
is Tugrik -> "Тугрик"
}
fun totalValueInRubels(): Double {
return amount * valueInRubels
}
}
Напишем код для щелчка кнопки. Вам нужно ввести минимальную и максимальную цену в любой валюте для одного котёнка, а кнопка покажет цену в рублях. Если вы увидите, что покупатель из Америки, то выставляете ценник в долларах. Если покупатель из непонятной страны, то ставьте тугрики (какая вам разница?).
convertButton.setOnClickListener {
val low = currencyFromSelection()
val high = currencyFromSelection()
low.amount = lowAmountEditText.text.toString().toDouble()
high.amount = highAmountEditText.text.toString().toDouble()
lowAmountInRubelsTextView.text = String.format("%.2f руб.", low.totalValueInRubels())
highAmountInRubelsTextView.text = String.format("%.2f руб.", high.totalValueInRubels())
}
private fun currencyFromSelection() =
when (currencies[currencySpinner.selectedItemPosition]) {
is Dollar -> Dollar()
is Rubel -> Rubel()
is Tugrik -> Tugrik()
}
В примере мы выставили цену от 2 до 3 долларов за котёнка (что-то мы продешевили) и сразу видим, сколько заработаем в рублях.