#android #kotlin #polymorphism #android-room
#Android #kotlin #полиморфизм #android-room
Вопрос:
Рассмотрим следующее Entity
:
@Entity(tableName = "media")
data class Media(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
// Stored as a JSON blob in SQLite using some TypeAdapter magic
val content: Content,
) {
sealed class Content {
data class Image(val width: Int, val height: Int): Content()
data class Video(val framerate: Int): Content()
}
}
Чтобы получить доступ Media.Content.Image.width
, я должен был бы сделать
val media: Media = // { ... } - returns image media
(media.content as Media.Content.Image).width
Это довольно быстро устаревает и кажется подверженным ошибкам.
С дженериками я мог бы сделать что-то вроде следующего:
@Entity(tableName = "media")
data class Media<T: Media.Content>(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val content: T,
) {
sealed class Content {
data class Image(val width: Int, val height: Int): Content()
data class Video(val framerate: Int): Content()
}
}
val media: Media<Media.Content.Image> = // { ... } - returns image media
media.content.width
Однако этот стиль кажется проблематичным:
error: Cannot use unbound fields in entities.
private final T content = null;
error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
private final T content = null;
Я не уверен T
, как будет выглядеть TypeConverter для — есть ли способ получить место для обработки этого типа дженериков или он просто не поддерживается?
Ответ №1:
Почему бы не использовать овеществленный встроенный метод, который обрабатывает приведение типов? Поскольку у вас уже нет никаких гарантий безопасности типов от компилятора.
Еще лучше было бы также сериализовать тип и использовать оператор switch для обработки всех возможных возвращаемых типов.
inline fun <reified T> Media.contentAccess(): T {
return this.content as T
}
val media: Media = Media(id = 1, content = Media.Content.Image(width = 1, height = 1))
media.contentAccess<Media.Content.Image>().width
Комментарии:
1. Это вариант, который я рассмотрел. Однако у меня есть некоторые проблемы с этим. 1. вы не можете создать, например, a
List<Media<Media.Content.Image>>
, 2. у вас есть два свойства для одних и тех же данных, 3.contentT<Media.Content.Image>()
такой же длины (или длиннее), какcontent as Media.Content.Image
(почему бы просто не использоватьas
в первую очередь?), 4. Теперь вам нужно использовать общий метод для каждого доступа . Используяas
напрямую, вы можете использовать интеллектуальные приведения для последовательного.content
использования.2. Я слышу все ваши опасения, и они полностью обоснованы. Проблема, с которой вы сталкиваетесь, заключается в том, что объявление объекта по сути представляет собой схему хранения, аналогичную сценарию SQL DDL, а не объекту транспортного уровня. Это означает, что тип T: Media . Содержимое — это характеристика способа запроса к БД. Если вы хотите создать более эргономичный api, то ваш DAO, вероятно, должен предлагать mapper, который преобразует мультимедиа в MediaWrapper<T>, когда вы можете использовать контекст вашего запроса для предоставления правильной семантики.