Menu
Coddy logo textTech

Объекты-компаньоны

Часть раздела Объектно-ориентированное программирование путешествия по 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.0

fromFahrenheit — это фабричная функция: функция, которая создаёт экземпляры. В отличие от конструктора, у неё описательное имя, и она может вернуть 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()
    }
}
challenge icon

Задание

Легко

Завершите класс 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}")
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Песочница Kotlin