Kotlin enforce implementing class to be a supertype of another type
Asked Answered
H

2

6

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?

Hassock answered 24/7, 2018 at 8:37 Comment(1)
There is no such feature in Kotlin. Even if Kotlin had one, it would still be possible for a Java class to implement a Kotlin interface, without any regard for Kotlin's own restrictions.Templin
D
5

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
    }
}
Disinfect answered 24/7, 2018 at 9:1 Comment(0)
E
5

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.

Exhaustive answered 24/7, 2018 at 9:3 Comment(1)
Ah, that's a nice use of extension functions.Disinfect

© 2022 - 2024 — McMap. All rights reserved.