Admittedly I haven’t clearly stated my intention to have MyAnimal
publicly exposed and implementing Animal
so it can be used directly as a type.
(NOTE: Now I edited my original post so as to clarify this point.)
Here we go.
Let’s say my animals, being particularly picky, unlike others’ animals, got a favorite color. LOL
So we proceed to define an additional property favoriteColor
:
abstract class MyAnimal internal constructor() : Animal {
abstract val favoriteColor: String
protected fun makeNoise(description: String) {
println("$name: $description")
}
}
data class MyCat(
override val name: String,
override val favoriteColor: String
) : MyAnimal(), Cat {
override fun meow() {
makeNoise(description = "meow!")
}
}
data class MyDog(
override val name: String,
override val favoriteColor: String
) : MyAnimal(), Dog {
override fun bark() {
makeNoise(description = "bark!")
}
}
Then we may wish to randomly create cats and dogs of my “color-picky” variation:
fun myRandomAnimal(
name: String,
favoriteColor: String
): MyAnimal =
when (Random.nextInt(0, 1)) {
0 -> MyCat(name = name, favoriteColor = favoriteColor)
1 -> MyDog(name = name, favoriteColor = favoriteColor)
else -> error("Broken random implementation")
}
At this point, we may want to create one of those “color-picky” animals:
fun main() {
val myColorPickyAnimal = myRandomAnimal(
name = "Weirdo",
favoriteColor = "beige"
)
println("${myColorPickyAnimal.name}'s favorite color is ${myColorPickyAnimal.favoriteColor}")
}
Alright, everything feels fine… Except that it’s not. My animals are still animals, for as weird and color-picky as they may be — as such, MyAnimal
should still implement Animal
. A case where it’d be useful follows.
Let’s say we want to log every animal’s name, regardless of whether they’re my weirdos or not. So we got the following function:
fun logAnimal(animal: Animal) {
println("${animal.name} is here!")
}
…and now we need to apply logAnimal
to myColorPickyAnimal
:
fun main() {
val myColorPickyAnimal = myRandomAnimal(
name = "Weirdo",
favoriteColor = "beige"
)
logAnimal(animal = myColorPickyAnimal) // Error: `MyAnimal` is not assignable to `Animal`
println("${myColorPickyAnimal.name}'s favorite color is ${myColorPickyAnimal.favoriteColor}")
}
We can’t, since myColorPickyAnimal
is typed MyAnimal
, which is not assignable to Animal
.
Ultimately,
Well, that’s right. Both your and @broot’s suggestions are valid, in different circumstances — though they don’t solve in an ideal way the issue I’m having, which definitely does apply to several scenarios, as I pointed out in my original post.