# Class Delegation
Both composition and inheritance place subobjects inside your new class. With composition the subobject is explicit and with inheritance it is implicit.
Composition uses the functionality of an embedded object but does not expose its interface. For a class to reuse an existing implementation and implement its interface, you have two options: inheritance and class delegation.
Class delegation is midway between inheritance and composition. Like composition, you place a member object in the class you’re building. Like inheritance, class delegation exposes the interface of the subobject. In addition, you can upcast to the member type. For code reuse, class delegation makes composition as powerful as inheritance.
How would you achieve this without language support? Here, a spaceship needs a control module:
// ClassDelegation/SpaceShipControls.kt
package classdelegation
interface Controls {
fun up(velocity: Int): String
fun down(velocity: Int): String
fun left(velocity: Int): String
fun right(velocity: Int): String
fun forward(velocity: Int): String
fun back(velocity: Int): String
fun turboBoost(): String
}
class SpaceShipControls : Controls {
override fun up(velocity: Int) =
"up $velocity"
override fun down(velocity: Int) =
"down $velocity"
override fun left(velocity: Int) =
"left $velocity"
override fun right(velocity: Int) =
"right $velocity"
override fun forward(velocity: Int) =
"forward $velocity"
override fun back(velocity: Int) =
"back $velocity"
override fun turboBoost() = "turbo boost"
}
If we want to expand the functionality of the controls or adjust some commands, we might try inheriting from SpaceShipControls
. This doesn’t work because SpaceShipControls
is not open
.
To expose the member functions in Controls
, you can create an instance of SpaceShipControls
as a property and explicitly delegate all the exposed member functions to that instance:
// ClassDelegation/ExplicitDelegation.kt
package classdelegation
import atomictest.eq
class ExplicitControls : Controls {
private val controls = SpaceShipControls()
// Delegation by hand:
override fun up(velocity: Int) =
controls.up(velocity)
override fun back(velocity: Int) =
controls.back(velocity)
override fun down(velocity: Int) =
controls.down(velocity)
override fun forward(velocity: Int) =
controls.forward(velocity)
override fun left(velocity: Int) =
controls.left(velocity)
override fun right(velocity: Int) =
controls.right(velocity)
// Modified implementation:
override fun turboBoost(): String =
controls.turboBoost() + "... boooooost!"
}
fun main() {
val controls = ExplicitControls()
controls.forward(100) eq "forward 100"
controls.turboBoost() eq
"turbo boost... boooooost!"
}
The functions are forwarded to the underlying controls
object, and the resulting interface is the same as if you had used regular inheritance. You can also provide implementation changes, as with turboBoost()
.
Kotlin automates the process of class delegation, so instead of writing explicit function implementations as in ExplicitDelegation.kt
, you specify an object to use as a delegate.
To delegate to a class, place the by
keyword after the interface name, followed by the member property to use as the delegate:
// ClassDelegation/BasicDelegation.kt
package classdelegation
interface AI
class A : AI
class B(val a: A) : AI by a
Read this as “class B
implements interface AI
by using the a
member object.” You can only delegate to interfaces, so you can’t say A by a
. The delegate object (a
) must be a constructor argument.
ExplicitDelegation.kt
can now be rewritten using by
:
// ClassDelegation/DelegatedControls.kt
package classdelegation
import atomictest.eq
class DelegatedControls(
private val controls: SpaceShipControls =
SpaceShipControls()
): Controls by controls {
override fun turboBoost(): String =
"${controls.turboBoost()}... boooooost!"
}
fun main() {
val controls = DelegatedControls()
controls.forward(100) eq "forward 100"
controls.turboBoost() eq
"turbo boost... boooooost!"
}
When Kotlin sees the by
keyword, it generates code similar to what we wrote for ExplicitDelegation.kt
. After delegation, the functions of the member object are accessible via the outer object, but without writing all that extra code.
Kotlin doesn’t support multiple class inheritance, but you can simulate it using class delegation. In general, multiple inheritance is used to combine classes that have completely different functionality. For example, suppose you want to produce a button by combining a class that draws a rectangle on the screen with a class that manages mouse events:
// ClassDelegation/ModelingMI.kt
package classdelegation
import atomictest.eq
interface Rectangle {
fun paint(): String
}
class ButtonImage(
val width: Int,
val height: Int
): Rectangle {
override fun paint() =
"painting ButtonImage($width, $height)"
}
interface MouseManager {
fun clicked(): Boolean
fun hovering(): Boolean
}
class UserInput : MouseManager {
override fun clicked() = true
override fun hovering() = true
}
// Even if we make the classes open, we
// get an error because only one class may
// appear in a supertype list:
// class Button : ButtonImage(), UserInput()
class Button(
val width: Int,
val height: Int,
var image: Rectangle =
ButtonImage(width, height),
private var input: MouseManager = UserInput()
): Rectangle by image, MouseManager by input
fun main() {
val button = Button(10, 5)
button.paint() eq
"painting ButtonImage(10, 5)"
button.clicked() eq true
button.hovering() eq true
// Can upcast to both delegated types:
val rectangle: Rectangle = button
val mouseManager: MouseManager = button
}
The class Button
implements two interfaces: Rectangle
and MouseManager
. It can’t inherit from implementations of both ButtonImage
and UserInput
, but it can delegate to both of them.
Notice that the definition for image
in the constructor argument list is both public
and a var
. This allows the client programmer to dynamically replace the ButtonImage
.
The last two lines in main()
show that a Button
can be upcast to both of its delegated types. This was the goal of multiple inheritance, so delegation effectively solves the need for multiple inheritance.
- -
Inheritance can be constraining. For example, you cannot inherit a class when the superclass is not open
, or if your new class is already extending another class. Class delegation releases you from these and other limitations.
Use class delegation with care. Among the three choices—inheritance, composition and class delegation—try composition first. It’s the simplest approach and solves the majority of use cases. Inheritance is necessary when you need a hierarchy of types, to create relationships between those types. Class delegation can work when those options don’t.
Exercises and solutions can be found at www.AtomicKotlin.com.