Создание абстрактного класса с универсальным типом в Kotlin

#android #kotlin #generics

#Android #kotlin #обобщения

Вопрос:

Я так запутался, я искал, как работают дженерики kotlin, но я совсем не понимаю.

В принципе, у меня есть BaseAnimalDetail класс, который используется с некоторыми классами, расширяющими Animal класс. На данный момент when в BaseAnimalDetail классе их много, поэтому моя цель — избежать всего этого when , просто расширив BaseAnimalDetail whenever I need, for each class that extends Animal `

 open class BaseAnimalDetail<T : Animal?> : LinearLayout{
    private var animal: T? = null
    
    fun setAnimal(animal:T){
        this.animal = animal
    }

    private fun play() {
        when(animal){
           is Horse -> playWithHorse...
           is Dog -> playWithDog...
           is Cat -> playWithCat...
        }
        animal?.let { it.play() } //this is not working but animal?.play works fine, WHY?
    }
    ...More code
}
  

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

Приведенный выше код — всего лишь пример, но он поможет мне понять, как работают дженерики в Kotlin. Для меня обязательно иметь переменную T в базовом классе

Кроме animal?.let { it.play() } того, ПОЧЕМУ не компилируется?

Комментарии:

1. Можете ли вы опубликовать свой класс Animal?

Ответ №1:

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

Кстати, поскольку у Kotlin есть свойства, излишне делать animal private и иметь функцию setter .

 abstract class BaseAnimalDetail<T : Animal> {
    var animal: T? = null
    abstract fun play()
}

class CatDetail: BaseAnimalDetail<Cat>(){
    override fun play() {
        println("toss yarn to $animal")
    }
}
  

Но если все эти разные функции различны для каждого типа Animal, мне кажется, что у вас вообще не должно быть отдельного класса BaseAnimalDetail, и его функциональность должна быть только в различных классах Animal. В качестве альтернативы, если есть определенные модели поведения, которые разделяют некоторые животные, вы можете создавать различные классы поведения, которые можно добавлять в класс Animal (стратегия разработки композиции).

Комментарии:

1. Я не понимаю, зачем вы создали этот конструктор. У меня есть метод setAnimal, чтобы установить его, когда мне нужно. И для меня обязательно иметь переменную animal T, чтобы использовать ее из BaseAnimalDetail, когда это необходимо

2. Свойство не обязательно должно быть в конструкторе (это просто для удобства), но setAnimal оно полностью избыточно в Kotlin, потому animal что является свойством. Он имеет подразумеваемый получатель и установщик с вспомогательным полем. Вы можете настроить установщик на сайте объявления позже, не изменяя никаких подписей, точно так же, как с установщиком Java.

3. Я обновил его, чтобы извлечь свойство из конструктора. Однако это не имеет отношения к тому, что вы спрашивали, не так ли?

4. Мне только что пришло в голову, что, возможно, вы не знали, что свойства в конструкторе так же действительны, как и свойства вне конструктора. Они не являются частными для конструктора, но доступны для использования в течение всего срока службы объекта, как и другие свойства. Помещение их в конструктор позволяет удобно устанавливать их при создании экземпляра класса.

Ответ №2:

Не уверен, правильно ли вы понимаете, но то, что вы могли бы сделать, это то, что вы сказали — преобразуйте свой BaseAnimalDetail абстрактный класс в абстрактный класс с помощью abstract fun fun play() и разрешите Animal class расширять BaseAnimalDetail или разрешать ur Horse , Dog расширять его напрямую.

 abstract class BaseAnimalDetail {
    abstract fun play()
}

open class Animal : BaseAnimalDetail() {
    override fun play() = Unit
}

class Horse : Animal() {

    override fun play() {
        playWithHorse()
    }

    private fun playWithHorse() {}
}

class Dog : BaseAnimalDetail() {

    override fun play() {
        playWithDog()
    }

    private fun playWithDog() {}
}
  

Также интерфейс может предоставлять «всякий раз, когда мне нужно», поскольку наша цель — предоставить функцию, и в ней нет более глубокой логики.

Комментарии:

1. Для меня обязательно иметь переменную animal T, чтобы использовать ее из BaseAnimalDetail, когда это необходимо, кроме того, вы знаете, почему animal? .пусть { it.play() } не компилируется? Спасибо

2. Для чего ошибка animal?.let { it.play() } ? Похоже, он должен работать нормально, хотя это просто подробный способ написания animal?.play() .

3. Ошибка вывода типа. Ожидаемое несоответствие типов: требуется: найдено животное: T!

4. Я думаю, вы удалили слишком много контекста того, что происходит вокруг, чтобы мы могли увидеть проблему. То, что у вас есть выше, отлично работает как есть: pl.kotl.in/hvAe2OnNy

5. Эта ошибка может возникнуть, если play функция имеет возвращаемый тип.

Ответ №3:

Не уверен, зачем вам для этого нужны дженерики, вот моя реализация

 abstract class Animal {
    abstract fun play()
}

class Horse : Animal() {
    override fun play() {
        playWithHorse()
    }
    private fun playWithHorse() {}
}

class Dog : Animal() {
    override fun play() {
        playWithDog()
    }
    private fun playWithDog() {}
}


open class BaseAnimalDetail(var animal: Animal?=null) {
    private fun play() {
       animal?.play()
    }
}

class HorseDetail(var horse: Horse? = null): BaseAnimalDetail(horse){

}
  

Вы можете удалить play функцию из BaseAnimalDetail, вместо этого вызывая ее напрямую horse?.play() .

Комментарии:

1. Мой код — всего лишь пример чего-то более сложного, что мне нужно, мне нужно иметь переменную в BaseAnimalDetail (не в конструкторе). Спасибо

2. Это аргумент по умолчанию, поэтому вам не нужно передавать значение в конструкторе.

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