Generi c Дисперсия для делегированных свойств ReadOnly и ReadWrite в Kotlin - PullRequest
1 голос
/ 01 апреля 2020

Я понимаю ковариацию и контравариантность. Я также прочитал отличный пост Эри c Липперта здесь .

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

interface ReadOnlyProperty<in R, out T> {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
}

interface ReadWriteProperty<in R, T> {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
    operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}

Поскольку делегаты свойств переводятся компилятором, трудно найти практический пример, когда в игру вступает дисперсия. Мне трудно обернуть голову, я уверен, что есть причина.

Может кто-нибудь помочь объяснить это?

1 Ответ

0 голосов
/ 02 апреля 2020

Интерфейсы делегированных свойств не очень полезны для иллюстрации отклонений, потому что они являются необязательными, и вы никогда не вызываете эти функции напрямую. И вы вряд ли построите иерархию классов с ними или назначите их экземпляры переменным.

Дисперсия в этом случае бесполезна, а просто предполагает совместимость.

В случае делегатов thisRef имеет контравариантный тип, указывающий, что делегат только для чтения, который предназначен для работы в определенном классе, также может использоваться в подклассах этого класса.

Предположим, у вас есть класс Cat, который мяукает, и реализация делегата, которая делает его мяу, когда вы читаете делегированное свойство:

open class Cat {
    fun meow() {
        println("meow")
    }
}

class MeowingDelegate<T>(private val value: T): ReadOnlyProperty<Cat, T> {
    override fun getValue(thisRef: Cat, property: KProperty<*>): T {
        thisRef.meow()
        return value
    }
}

Вы можете использовать этот делегат в подклассе Cat, потому что подкласс будет иметь методы суперкласса, поэтому подкласс также может мяукать. Подпись предполагает, что MeowingDelegate с его типом in Cat квалифицируется как подкласс делегата с более конкретным типом c и поэтому может использоваться в подклассе Cat.

open class Kitten: Cat() {
    val x: Int by MeowingDelegate(1)
}

Редактировать: Для коварианта T для ReadOnlyProperty это означает, что делегат, который предоставляет один тип объекта, может также использоваться в качестве поставщика супертипа этого объекта. Поэтому, если бы у меня был делегат, который предоставляет котят, его также можно использовать для предоставления кошек:

open class Cat
open class Kitten: Cat() 

class KittenDelegate: ReadOnlyProperty<Any?, Kitten> {
    override fun getValue(thisRef: Any?, property: KProperty<*>): Kitten {
        return Kitten()
    }
}

class Sample {
    val cat: Cat by KittenDelegate()
}

Поскольку тип T является инвариантным для ReadWriteProperty, вы не можете сделать это с делегатом, который можно только вернуть котят. Это имеет смысл, потому что делегат также должен позволять вам устанавливать свойство, и поэтому он не сможет принять любого кота и рассматривать его как котенка.

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

Только для чтения List имеет ковариантный тип. Это позволяет вам легко приводить коллекции к более конкретным c типам:

class Kennel (val cats: List<Cats>)

val kittens: List<Kitten> = listOf(Kitten(), Kitten())
val kennel = Kennel(kittens) // OK

Вы не можете сделать это с MutableList, так как тип является инвариантным.

class Kennel (val cats: MutableList<Cats>)

val kittens: MutableList<Kitten> = mutableListOf(Kitten(), Kitten())
val kennel = Kennel(kittens) // Compiler error

Но это необходимо, чтобы MutableList был определен с инвариантным типом. MutableList<Kitten> не может принять любой тип Cat, который будет добавлен к нему, поэтому не имеет смысла иметь возможность привести его к MutableList<Cat>. И наоборот, MutableList<Cat> не обязательно содержит исключительно котят, поэтому нет смысла приводить его к MutableList<Kitten>. Конечно, на сайте использования вы можете дать ему дисперсию в зависимости от ваших потребностей, и именно поэтому MutableList<Kitten> может быть приведено к MutableList<out Cat>.

Для контравариантности на уровне класса я могу не думаю о распространенном классе stdlib, который есть в объявлении. Итак, предположим, мы создали тот, который домашние животные кошек. Тогда у нас есть котята, которых мы хотим погладить. CatPetter было бы достаточно для использования в качестве Petter<Kitten>, потому что котята считаются кошками и поэтому могут быть домашними животными на CatPetter. Если бы тип Petter был инвариантным, это было бы недопустимо.

open class Cat {
    fun purr() {
        println("rrrr")
    }
}
open class Kitten: Cat()

interface Petter<in T: Cat> {
    fun pet(recipient: T)
}

class CatPetter: Petter<Cat> {
    override fun pet(recipient: Cat) {
        recipient.purr()
    }
}

class BoxOfKittens (val petter: Petter<Kitten>)

val x = BoxOfKittens(CatPetter()) // OK
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...