xinaiz
xinaiz

Reputation: 7788

Kotlin enforce implementing class to be a supertype of another type

Since multiple inheritance is not allowed in java/kotlin, it's usefull to take advantage of interface default methods. Given example:

abstract class Animal { 
    fun beAnimal() { 
        println("I'm animal!") 
    } 
}
abstract class Mammal : Animal()  { 
    fun produceMilk() { 
         beAnimal().apply { println("Freesh milk!") }
    } 
}
abstract class AnimalWithBeak : Animal()  { 
    fun quack() { 
        beAnimal().apply { println("Quack!") }
    } 
}
class Platypus : ??? // I want it to both produce milk and quack!

As noted above, multiple base classes are not allowed, but we can use interfaces:

abstract class Animal { fun beAnimal() { println("I'm animal!") } }

interface Mammal { 
    fun produceMilk() { 
        (this as Animal).beAnimal().apply { println("Freesh milk!") }
    } 
}
interface AnimalWithBeak { 
    fun quack() { 
        (this as Animal).beAnimal().apply { println("Quack!") }
    } 
}
class Platypus : Animal(), Mammal, AnimalWithBeak {
    fun bePlatypus() {
        quack() // ok
        produceMilk() // ok
    }
}

Note that I don't own Animal class, but I still want to subclass it, and be able to mix these implementations. Example above is really simple, but in real code it would be extremely useful.

The problem is, that class that doesn't extend Animal can implement Mammal and AnimalWithBeak interfaces. In this case, the code will be broken as this as Animal cast will fail.

So the question - Is it possible to constraint interface inheritance only to specific classes? In this case, only classes that extend Animal should be allowed to implement Mammal and AnimalWithBeak interfaces.

Abstract syntax that probably doesn't exist could look something like that:

interface Mammal where this : Animal

But I quess it's not valid. Any solutions?

Upvotes: 6

Views: 3535

Answers (2)

Roland
Roland

Reputation: 23312

You can accomplish such a constraint with extension functions, but you may not supply your interface methods then anymore. However, as the Animal-class is not under your control you may want to add some other useful methods you desire with extension functions.

Example:

fun <T> T.produceMilk() where T : Animal, T : Mammal {
  beAnimal().apply { println("Freesh milk!") }
}

fun <T> T.quack() where T : Animal, T : AnimalWithBeak {
  beAnimal().apply { println("Quack!") }
}

fun main(args: Array<String>) {
  val myMammal = object : Mammal {} // a mammal, but not an animal
  // myMammal.produceMilk() // unresolved reference
  val myMammalAnimal = Platypus()
  myMammalAnimal.produceMilk() // works
}

Your classes/Interfaces then would look like:

abstract class Animal { fun beAnimal() { println("I'm animal!") } }

interface Mammal
interface AnimalWithBeak 
class Platypus : Animal(), Mammal, AnimalWithBeak {
  fun bePlatypus() {
    quack() // ok
    produceMilk() // ok
  }
}

The forced tight coupling you are asking for can otherwise be accomplished with @marstrans answer. That solution forces you to always have an animal when implementing the interfaces.

Upvotes: 5

marstran
marstran

Reputation: 28056

You can not constrain which classes an interface can be implemented by. However, if you want to avoid the casting, you can give the interface a property of type Animal that the implementing classes have to override. This will at least ensure that the implementing classes have an Animal object available.

abstract class Animal { fun beAnimal() { println("I'm animal!") } }

interface Mammal { 
    val animal: Animal

    fun produceMilk() { 
        animal.beAnimal().apply { println("Freesh milk!") }
    } 
}

interface AnimalWithBeak {
    val animal: Animal

    fun quack() { 
        animal.beAnimal().apply { println("Quack!") }
    } 
}

class Platypus : Animal(), Mammal, AnimalWithBeak {
    override val animal = this

    fun bePlatypus() {
        quack() // ok
        produceMilk() // ok
    }
}

Upvotes: 5

Related Questions