Search code examples
kotlinpolymorphismabstract-class

Is there a way to override an abstract property with a subtype of the declared type?


Consider the following example: I have an abstract class for Animal, and every animal has a mouth, but because every animal's mouth is different, the mouth class is also abstract:

abstract class Animal {
    var numberLegs: Int = 4
    var mouth: Mouth? = null
} 

abstract class Mouth {
    abstract fun makeSound()
}

I can now create a Dog and a DogMouth:

class Dog: Animal() {
    override var mouth: Mouth = DogMouth()
}

class DogMouth: Mouth() {
    override fun makeSound() {
        println("Bark!")
    }
}

But this allows me to also assign other types of mouths to the dog, which I don't want, e.g.:

class CatMouth: Mouth() {
    override fun makeSound() {
        println("Meow!")
    }
}

fun main() {
    val dog = Dog()
    dog.mouth.makeSound()   // will print "Bark!"
    dog.mouth = CatMouth()  // I don't want this to work
    dog.mouth.makeSound()   // will print "Meow!"
}

And setting override var mouth: DogMouth = DogMouth() doesn't work.

How can I make sure that Dogs only have DogMouths (and other dog body parts)?


Solution

  • Similar problems are addressed here and here. The solution is to use a generic parameter:

    abstract class Animal<MouthType: Mouth> {
        var numberLegs: Int = 4
        abstract var mouth: MouthType
    } 
    
    class Dog: Animal<DogMouth>() {
        override var mouth: DogMouth = DogMouth()
    }
    

    This makes dog.mouth = CatMouth() fail with a type mismatch.

    With more body parts extra generics need to be added:

    abstract class Animal<MouthType: Mouth, EarType: Ear, TailType: Tail> {
        var numberLegs: Int = 4
        abstract var mouth: MouthType
        abstract var ear: EarType
        abstract var tail: TailType
    }