Another choice for middle ground (Composition Vs Inheritance)
Both composition and inheritance place sub objects inside the new class. With composition, the sub object is explicit whereas the sub object with inheritance is implicit.
Class delegation is midway between composition and inheritance. Like composition, we have to place the member inside our class. Like inheritance, it exposes the interface of the sub object.
Let’s see this:
interface Control {
fun up(speed: Int): String
fun down(speed: Int): String
fun left(speed: Int): String
fun right(speed: Int): String
}
class DroneController: Control {
override fun up(speed: Int) = "up $speed"
override fun down(speed: Int) = "down $speed"
override fun left(speed: Int) = "left $speed"
override fun right(speed: Int) = "right $speed"
}
What if we want a super drone controller that can speed 2 times while going up and down? What about inheriting DroneController
? But it doesn’t work because DroneController
is not open
.
We can create an instance of DroneController
as a property and explicitly delegate all the exposed member functions to that instance.
class SuperDroneController: Control {
private val controller = DroneController()
// delegate
override fun up(speed: Int)
= controller.up(speed) + "2x"
override fun down(speed: Int)
= controller.down(speed) + "2x"
override fun left(speed: Int) = controller.left(speed)
override fun right(speed: Int) = controller.right(speed)
}
It is called Delegation Design Pattern which allows object composition to achieve the same code reuse as inheritance.
Kotlin by
keyword
For the SuperDroneController
example, in Kotlin language, we can remove the boilerplate code with the keyword by
.
class SuperDroneController(dController: DroneController) :
Control by dController {
override fun up(speed: Int)
= dController.up(speed) + "2x"
override fun down(speed: Int)
= dController.down(speed) + "2x"
}
Use case: Multiple Class inheritance
Kotlin doesn’t support multiple class inheritance, but you can simulate it using class delegation. For example, you want to produce a button by combining a class that draws a rectangle and another class that manages mouse events.
// a class drawing rectangle
interface Rectangle {
fun draw(): String
}
class ButtonImage (val w: Int, val h: Int) : Rectangle {
override fun draw() =
"drawing with $w and $h"
}
Next is the mouse event manager.
interface MouseManager {
fun click(): Boolean
}
class UserInput : MouseManager {
override fun click() = true
}
Finally, here is our Button
.
class Button(image: Rectangle, input: MouseManager) :
Rectangle by image, MouseManager by input
fun main() {
val buttonImage = ButtonImage(10, 5)
val userInput = UserInput()
button = Button(buttonImage, userInput)
button.draw()
button.click()
}
Summary
Inheritance can be constraining. We cannot inherit a class when the superclass is not open
or our class is already extending another class. Class delegation can help us with that.
Usually, there are 3 choices: inheritance, composition and class delegation. Try composition first. it is the simplest one and solves most of use cases. If you really need a hierarchy of type, then go for inheritance. If none of them can work, then think about class delegation.