Наследование классов и параметризованные типы в Kotlin: возможен ли полиморфизм подтипа с помощью дженериков? - PullRequest
0 голосов
/ 14 мая 2018

Я изо всех сил пытаюсь понять и / или заставить дженерики и полиморфизм Kotlin работать на меня.Рассмотрим этот код:

class Item<T: BaseAttributes> {
    var id: Long = -1L
    lateinit var type: String
    lateinit var attributes: T
}

open class BaseAttributes {
    lateinit var createdAt: String
    lateinit var updatedAt: String
}

open class BaseResponseList<T : BaseAttributes> {
    lateinit var items: List<Item<T>> // the collection of items fetched from an API 
}

class FruitAttributes(val id: Long, val color: String /* ... */) : BaseAttributes()

class FruitResponseList: BaseResponseList<FruitAttributes>()

// base service for all types of items
interface ApiService {
    fun getItems(): BaseResponseList<BaseAttributes>
    // fun getItemById(itemId: Long): BaseResponse<BaseAttributes>
    /* other CRUD functions here ... */
}

// service for fruits
interface FruitService: ApiService {
    override fun getItems(): FruitResponseList // get fruit items
}

Я озадачен этой ошибкой компилятора, которая предполагает, что FruitResponseList не является подтипом параметризованного базового класса (BaseResponseList<FruitAttributes>):

Return type of 'getItems' is not a subtype of the return type of the overridden member 'public abstract fun getItems(): BaseResponseList<BaseAttributes> defined in ApiService'

Я пытаюсь использовать ковариацию сайта объявлений в BaseAttributes, чтобы сообщить компилятору о моем намерении, что FruitResponseList является подклассом базового списка ответов, как это:

open class BaseResponseList<out T : BaseAttributes> {
    lateinit var items: List<Item<T>> // the collection of items fetched from an API 
}

приводит к этой ошибке:

Type parameter T is declared as 'out' but occurs in 'invariant' position in type List<Item<T>>

Как мне получить отношение типа-подтипа между списками ответов Fruit & Base?

Контекст

Я реализуюсетевой код для выполнения операций CRUD с API, который основан на формате JSON API spec , поэтому я создал атрибуты и классы данных (Item) для представления объектов ответа json.

Моя цель - уменьшить количество дублирующегося кода, чтобы мне приходилось писать декларации службы API только один раз для каждой сущности в моем приложении (фрукты, поставщики, покупатели и т. Д.).Я также хочу избежать написания дублированных / стандартных реализаций уровней хранилища данных для каждой сущности в моем приложении (в контексте чистой архитектуры).Я должен иметь возможность просто указать специфичные для бизнес-сущности типы (модели / сущности) и позволить одной общей реализации выполнять выборку сетевых данных.

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

Ответы [ 3 ]

0 голосов
/ 15 мая 2018

Я озадачен этой ошибкой компилятора, которая предполагает, что FruitResponseList не является подтипом параметризованного базового класса (BaseResponseList<FruitAttributes>):

Это подтип BaseResponseList<FruitAttributes>, который не является подтипом BaseResponseList<BaseAttributes>.

Я пытаюсь использовать ковариацию сайта объявлений в BaseAttributes, чтобы сообщить компилятору о моем намерении, что FruitResponseList является подклассом базового списка ответов, напримерэто: ...

Это может быть правильным подходом, но проблема в том, что Item не является ковариантным (и это не может быть, потому что attributes является var и егоСеттер принимает параметр T).Если Item можно изменить, чтобы избежать этого, хорошо.

Другой подход заключается в добавлении параметра типа в ApiService:

// base service for all types of items
interface ApiService<T: BaseAttributes> {
    fun getItems(): BaseResponseList<T>
    // fun getItemById(itemId: Long): BaseResponse<T>
    /* other CRUD functions here ... */
}

// service for fruits
interface FruitService: ApiService<FruitAttributes> {
    override fun getItems(): FruitResponseList // get fruit items
}
0 голосов
/ 18 мая 2018

Ответы, предоставленные Марком и Алексеем, были очень полезны.В частности, параметризация ApiService на BaseAttributes была более гибкой опцией, поскольку она позволяла функциям принимать или возвращать оба типа BaseResponseList и BaseResponse [sub].

Однако, несмотря ни на что, оказывается, что библиотека Retrofit не позволяет своим объявлениям служб расширять другие интерфейсы или даже параметризовываться, предположительно в пользу композиции по сравнению с наследованием.Здесь много споров об их решении по этому вопросу .В итоге я создал отдельные интерфейсы для каждой из моих моделей: /

0 голосов
/ 15 мая 2018

Почему бы не что-то вроде этого:

// base service for all types of items
interface ApiService<T> {
    fun getItems(): T
    // fun getItemById(itemId: Long): BaseResponse<BaseAttributes>
    /* other CRUD functions here ... */
}

// service for fruits
interface FruitService: ApiService<FruitResponseList> {

    override fun getItems(): FruitResponseList
}

или вот так:

// base service for all types of items
interface ApiService<T : BaseAttributes> {
    fun getItems() : BaseResponseList<T>
    // fun getItemById(itemId: Long): BaseResponse<BaseAttributes>
    /* other CRUD functions here ... */
}

// service for fruits
interface FruitService: ApiService<FruitAttributes> {

    override fun getItems(): FruitResponseList
}

В вашей текущей реализации вы будете ограничены BaseResponseList<BaseAttributes>.

...