Reputation: 7788
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
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
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