Reputation: 183
I am struggling with generics / interfaces / implementation in kotlin.
The question is why I cannot to pass a implementation to HabitsRepository like this:
class HabitsRepository(habitDao: HabitDao, habitConverter: HabitConverter) :
AbstractRepository<AbstractDataSource<AbstractEntity>, AbstractConverter<AbstractModel, AbstractEntity>>(
habitDao,
habitConverter
)
I got following error
My setup is following:
HabitsRepository
class HabitsRepository(habitDao: HabitDao, habitConverter: HabitConverter) :
AbstractRepository<AbstractDataSource<AbstractEntity>, AbstractConverter<AbstractModel, AbstractEntity>>(
habitDao as AbstractDataSource<AbstractEntity>,
habitConverter as AbstractConverter<AbstractModel, AbstractEntity>
)
HabitDao
@Dao
interface HabitDao : AbstractDataSource<Habit>{
@Query("SELECT * FROM habit WHERE id=:id ")
override fun getById(id: String): Single<Habit>
@Query("SELECT * FROM habit")
override fun getAll(): Single<List<Habit>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
override fun insert(entity: AbstractEntity): Completable
@Delete
override fun delete(habit: AbstractEntity) : Completable
@Update
override fun update(habit: AbstractEntity): Completable
}
HabitConverter
class HabitConverter : AbstractConverter<HabitModel, Habit> {
override fun toModel(entity: Habit): HabitModel {
return HabitModel()
}
override fun toEntity(model: HabitModel): Habit {
return Habit()
}
}
HabitDatabaseSource
class HabitDatabaseSource @VisibleForTesting constructor(private val habitDao: HabitDao) : HabitDao {
override fun getById(id: String): Single<Habit> = habitDao.getById(id)
override fun getAll(): Single<List<Habit>> = habitDao.getAll()
override fun insert(entity: AbstractEntity): Completable = habitDao.insert(entity)
override fun delete(habit: AbstractEntity): Completable = habitDao.delete(habit)
override fun update(habit: AbstractEntity): Completable = habitDao.update(habit)
}
AbstractDataSource
interface AbstractDataSource<T : AbstractEntity> {
fun getById(id: String): Single<T>
fun getAll(): Single<List<T>>
fun insert(entity: AbstractEntity): Completable
fun delete(entity: AbstractEntity): Completable
fun update(entity: AbstractEntity): Completable
}
AbstractEntity / AbstractModel
abstract class AbstractEntity
abstract class AbstractModel
AbstractConverter
interface AbstractConverter<AM: AbstractModel, AE: AbstractEntity>{
fun toModel(entity: AE) : AM
fun toEntity(model: AM): AE
}
AbstractRepository
abstract class AbstractRepository<ADS: AbstractDataSource<AbstractEntity>, AC: AbstractConverter<AbstractModel, AbstractEntity>>(
private val abstractDataSource: ADS,
private val abstractConverter: AC
){
fun getById(id: String): Single<AbstractModel> {
return abstractDataSource.getById(id).map { abstractConverter.toModel(it) }
}
fun getAll(): Single<MutableList<AbstractModel>> =
abstractDataSource.getAll().flattenAsObservable {it}.map { abstractConverter.toModel(it) }.toList()
fun insert(model: AbstractModel): Completable = abstractDataSource.insert(abstractConverter.toEntity(model))
fun delete(model: AbstractModel): Completable = abstractDataSource.delete(abstractConverter.toEntity(model))
fun update(model: AbstractModel): Completable = abstractDataSource.update(abstractConverter.toEntity(model))
}
Upvotes: 0
Views: 351
Reputation: 93872
HabitDao
is not an AbstractDataSource<AbstractEntity>
. It is defined as AbstractDataSource<Habit>
. When a generic type is not defined as in
or out
, then it is invariant, meaning you must pass a parameter with the exact type, not a supertype or subtype. There is a similar issue for your HabitConverter
and both of its superclass's generic types.
And taking it even a step farther, your HabitDao
is a subclass of AbstractDataSource
so you are breaking invariance at two levels.
Your HabitRepository
must declare the exact types of the Habit classes it is using:
class HabitsRepository(habitDao: HabitDao, habitConverter: HabitConverter) :
AbstractRepository<HabitDao, HabitConverter>(
habitDao,
habitConverter
)
At least with your AbstractRepository
, the generic types only occur in producing positions, so you could declare their types as out
without losing any functionality:
abstract class AbstractRepository<out ADS: AbstractDataSource<AbstractEntity>, out AC: AbstractConverter<AbstractModel, AbstractEntity>> ( //...
and then you could be less strict in your HabitsRepository
definition:
class HabitsRepository(habitDao: HabitDao, habitConverter: HabitConverter) :
AbstractRepository<AbstractDataSource<Habit>, AbstractConverter<HabitModel, Habit>>(
habitDao,
habitConverter
)
But AbstractDataSource
and AbstractConverter
are both producers and consumers of their generic types, so their types have to be invariant.
Upvotes: 4