Объекты-компаньоны
Часть раздела Объектно-ориентированное программирование путешествия по Kotlin на Coddy. Урок 13 из 57.
Некоторые члены принадлежат классу в целом, а не одному экземпляру. Kotlin помещает их в companion object внутри класса, и вы вызываете их через имя класса:
class Temperature(val celsius: Double) {
companion object {
fun fromFahrenheit(f: Double) = Temperature((f - 32) * 5 / 9)
}
}Внутри main:
val boiling = Temperature.fromFahrenheit(212.0)
println(boiling.celsius)Результат:
100.0fromFahrenheit — это фабричная функция: функция, которая создаёт экземпляры. В отличие от конструктора, у неё описательное имя, и она может вернуть null вместо объекта. При наличии private constructor фабрика — единственный способ создать экземпляр, поэтому она может сначала проверить и очистить входные данные:
class Username private constructor(val text: String) {
companion object {
fun of(raw: String): Username? {
val clean = raw.trim().lowercase()
return if (clean.length in 3..12) Username(clean) else null
}
}
}Внутри main:
println(Username.of(" Ada_L ")?.text)
println(Username.of("x")?.text)
// Username("x") // ошибка: конструктор является privateВывод:
ada_l
nullОбъект-компаньон принадлежит классу, поэтому он может вызывать приватный конструктор, а собственный код класса использует члены компаньона без префикса. Здесь каждый билет получает следующий номер из одного счётчика, общего для всех билетов:
class Ticket(val holder: String) {
val number = nextNumber++
companion object {
private var nextNumber = 1
val issued: Int
get() = nextNumber - 1
}
}Внутри main:
val a = Ticket("Ada")
val b = Ticket("Bo")
println("${a.holder} #${a.number}, ${b.holder} #${b.number}")
println("issued: ${Ticket.issued}")Вывод:
Ada #1, Bo #2
issued: 2В Kotlin нет ключевого слова static. Эту роль выполняют члены компаньона: одна копия для всего класса, к которой обращаются как к ClassName.member. Как и любой объект, компаньон создаётся один раз — при первом использовании класса. Размещай в нём то, что не зависит от отдельного экземпляра, например фабрики и общие счётчики; всё, что читает данные экземпляра, должно оставаться обычным членом.
class User private constructor(val name: String) {
fun greet() = "hi $name" // нужен экземпляр: user.greet()
companion object {
fun guest() = User("guest") // принадлежит классу: User.guest()
}
}Задание
ЛегкоЗавершите класс Coupon. Его конструктор является закрытым, поэтому купоны создаются фабрикой Coupon.create(code, percent) в сопутствующем объекте. Она возвращает null, если только код не состоит из 4–10 символов — заглавных букв и цифр, — а значение процента не находится в диапазоне от 1 до 90. Сопутствующий объект также подсчитывает созданные им купоны в created; внешний код может прочитать это значение, но не изменить его. apply(price) возвращает цену за вычетом скидки, округлённую вниз (price * (100 - percent) / 100).
Предоставленный код считывает строки SAVE10 10 200 (код, процент, цена). Он выводит SAVE10: 180 или invalid coupon SAVE10, а в конце — created: с количеством.
Ваш код должен находиться в Coupon.kt. В Main.kt содержится предоставленный код ввода/вывода, и его нельзя редактировать.
Попробуйте сами
fun main() {
// Предоставленный код ввода/вывода: оставьте его как есть
val input = generateSequence(::readLine).toList()
for (line in input) {
val (code, percent, price) = line.split(" ")
val coupon = Coupon.create(code, percent.toInt())
if (coupon == null) println("invalid coupon $code") else println("${coupon.code}: ${coupon.apply(price.toInt())}")
}
println("created: ${Coupon.created}")
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Классы и объекты
Работа с файламиОпределение классовПервичные конструкторыБлоки init и валидацияВторичные конструкторыПовторение — читательский билет4Наследование
Открытые классы и подклассыПереопределение членовsuper и конструкторыАбстрактные классыПовторение — каталог сотрудников7Специальные классы
Классы данныхКлассы перечисленийЗапечатанные иерархииequals, hashCode, toStringПовторение — трекер заказов10Паттерны проектирования. Часть 2
Делегирование с помощью byПаттерн декораторПаттерн строительПаттерн состояние2Свойства и инкапсуляция
Пользовательские аксессорыМодификаторы видимостиПриватные сеттерыlazy и lateinitПовторение — банковский счёт5Интерфейсы
Объявление интерфейсовМетоды по умолчаниюМножественные интерфейсыИнтерфейсы как типыПовторение — способы оплаты8Обобщения
Обобщённые функцииОбобщённые классыОграничения типовВариативность с in и outПовторение — очередь с приоритетом11Проект: Управление библиотекой
Книги и читателиВыдача книг3Объекты и компаньоны
Объявления объектовОбъекты-компаньоныКонстанты с constОбъектные выраженияПовторение — билетная касса6Полиморфизм
Динамическая диспетчеризацияПроверка типов и умные приведения типовwhen с типамиФункции-расширенияПовторение — смотритель зоопарка9Паттерны проектирования. Часть 1
Паттерн «Одиночка»Фабричный паттернПаттерн «Наблюдатель»Паттерн «Стратегия»Потренируйтесь самостоятельно: Песочница Kotlin