Я изо всех сил пытаюсь понять и / или заставить генерики и полиморфизм 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, поэтому я создал классы атрибутов и данных (Item) для представления объектов ответа json.
Моя цель - уменьшить количество дублированного кода, чтобы мне нужно было только один раз написать декларации службы API для каждой сущности в моем приложении (фрукты, поставщики, покупатели и т. д.). Я также хочу избежать написания дублированных / шаблонных реализаций уровней репозитория данных для каждого объекта в моем приложении (в контексте чистой архитектуры). Я должен иметь возможность просто указать типы, специфичные для бизнес-сущностей (модели / сущности), и позволить одной общей реализации выполнять работу по выборке сетевых данных.
Я подумал, что для этого имеет смысл использовать дженерики и наследование. В этом конкретном примере идея состоит в том, что GET, специфичный для фруктов, будет возвращать список ответов фруктов, который является подтипом базового списка ответов. Будем очень признательны за любые рекомендации по этому поводу или альтернативные подходы к этой проблеме.
Почему не что-то вроде этого:
// 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> в вашей текущей реализации.
I'm stumped by this compiler error that suggests
FruitResponseListis not a subtype of the parametrized base class (BaseResponseList<FruitAttributes>):
Это подтип BaseResponseList<FruitAttributes>, который не является подтипом BaseResponseList<BaseAttributes>.
I try to use declaration-site covariance in BaseAttributes to tell the compiler my intention that a FruitResponseList is a subclass of the base response list like this:...
Это может быть правильным подходом, но проблема в том, что 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
}
Ответы Марка и Алексея оказались очень полезными. В частности, параметризация ApiService на BaseAttributes была более гибким вариантом, поскольку она позволяла функциям принимать или возвращать оба типа BaseResponseList и BaseResponse [под].
Однако, что бы это ни стоило, оказывается, что библиотека Retrofit не позволяет своим объявлениям служб расширять другие интерфейсы или даже параметризоваться, предположительно в пользу композиции, а не наследования. По поводу их решения по этому проблема здесь ведется много споров. В итоге я создал отдельные интерфейсы для каждой из моих моделей: /
Предлагаемое чтение: docs.oracle.com/javase/tutorial/java/generics/inheritance.ht ml