Hello, everyone! Welcome to the article about class delegation in Kotlin. This tutorial will help you understand how to use class delegation in Kotlin and when to use it instead of inheritance.
The examples in this article are not mine and were taken from this article with some changes applied. It provides a good explanation but, to my mind, lacks some things crucial for understanding. So I brought them up here.
Why use delegation instead of inheritance
Let’s say we want a list’s item to be recoverable after deletion. To achieve this, we need to implement MutableList
, override the remove
function, and create our own recover
function. But to do this, we need to override other methods that the MutableInterface
tells us. So we essentially need to create our own implementation of MutableList
. That’s not a cool thing to do as all the code is already written for us.
To avoid creating our own list, we can extend ArrayList
instead. But this approach is not good for abstraction. We don’t want this list to be an ArrayList. We don’t need ArrayList
's specific methods like trimToSize
, ensureCapacity
, grow
, etc. We just want to have an additional function.
So there is a better way to achieve this. And this is delegation. We want to have only MutableList
's functions overridden by ArrayList
and our own implementation of remove
.
How the delegation pattern works
We need to create a new variable of the ArrayList
type and, at the same time, implement MutableList
. And then in all the overridden functions’ bodies, respective ArrayList
's methods should be called. For example,
class ListWithTrash <T>(override val size: Int) : MutableList<T> {
private val innerList: MutableList<T> = ArrayList()
// the variable for storing the last deleted item
private var deletedItem : T? = null
override fun remove(element: T): Boolean {
deletedItem = element
// calling ArrayList's method, not our implementation
return innerList.remove(element)
}
// here is our own function for recovering elements
fun recover(): T? {
return deletedItem
}
override fun add(element: T): Boolean {
// calling ArrayList's method, not our implementation
innerList.add(element)
}
}
In this example, we override only add
and remove
MutableList
's methods. But we have a bunch of other methods we have to override for this to compile. That’s a lot of boilerplate code!
How to delegate using by
keyword
Luckily, we don’t need to do this. That’s how it works internally. Kotlin provides first-party support for delegation, and all this is done automatically under the hood. A developer's task is to use the simple by
keyword:
class ListWithTrash <T>(
private val innerList: MutableList<T> = ArrayList()
) : MutableList<T> by innerList {
private var deletedItem : T? = null
override fun remove(element: T): Boolean {
deletedItem = element
return innerList.remove(element)
}
fun recover(): T? {
return deletedItem
}
}
In this example, the innerList
variable was also moved into the constructor to let the users of ListWithTrash
provide their own ArrayList
. This allows them to specify the desired list’s properties, for example, its size.
This is called delegation because along with our own implementations, the rest of the functions are delegated to the other classes. In our case, we delegate MutableList
implementation to ArrayList
. The necessary functions are “provided by” ArrayList
.
Again, ArrayList
's specific functions won’t work:
val listWithTrash = ListWithTrash<String>()
// listWithTrash.trimToSize() // → exception! (ListWithTrash doesn't have this function)
If you need them, extend from ArrayList
directly.
Multiple delegation is… allowed
Also remember that you're not restricted to just one delegate. Kotlin's way of implementing delegation is similar to traits
implementation in languages like Groovy. You can compose different functionality via delegates. Kotlin's way can also be considered more powerful because you can "plug in" different implementations too.
interface Marks {
…
Delegation vs Inheritance
Inheritance allows to get a ready implementation by extending from the respective class. Whereas class delegation allows making a custom inheritant without lowering the class in the hierarchy. This is done by “copying” the ready implementation from the delegate. Here is the scheme for our situation:
This scheme is not entirely correct because ArrayList
doesn’t extend from MutableList
in Kotlin. But the idea stays the same — our class (ListWithTrash
) and the delegate (ArrayList
) are on the same level in the hierarchy. Check out the cover of this article.
When to use which? Use inheritance only when you are sure that your class and the class you inherit from share all the common traits. If you think your class doesn’t need some variables or methods from its possible parent, and “is a” relationship doesn’t apply, it’s better to use delegation.
That’s it! If this article was helpful, please, like it and follow for more. If you have any questions or corrections, feel free to write in the comments.