Class Delegation
Inheritance is a powerful construct in object-oriented programming… sometimes a bit too powerful. You hear the expression “favor composition over inheritance” a lot, because inheritance is fairly rigid:
- A class can only extend one other class
- A class gets everything that it inherits (excluding
private
elements)
The combination of those two limitations means that crafting a class hierarchy can be a challenge, so that only the right things are available in the right classes and not elsewhere.
Kotlin offers a spin on inheritance and composition that can help alleviate some of this, in the form of class delegation. Just as we saw in the previous chapter that you can use the by
keyword to delegate a property, you can use by
to delegate the implementation of an interface.
Playing Favorites
Suppose that we have some collection of items that we want to show the user, perhaps in some sort of a list. Items can be marked as “favorites”, akin to browser bookmarks, so we need to track which items are presently the favorites. However, for various reasons, the way that we update the favorites is different than how we work with the items themselves — for example, there might be different Web service endpoints for dealing with favorites. So we want to keep the favorite business logic a bit separate from the items themselves.
We could say that we have a FavoriteStore
that tells us whether an item is marked as a favorite and to toggle that state for an item:
interface FavoriteStore<T> {
fun isFavorite(thingy: T): Boolean
fun toggleFavorite(thingy: T, isFavorite: Boolean)
}
Our actual Item
class does not track its favorite status — we instead would get that from some instance of FavoriteStore
:
data class Item(val id: String, val name: String)
Let’s further suppose that this is being done in an Android app, one that has already adopted the Jetpack ViewModel
system. So we have some sort of ViewModel
that will handle displaying a collection of Item
objects. However, that display not only includes the data from the Item
itself, but also whether or not the Item
is a favorite, so we can have an icon designating that status.
Somehow, consumers of our theoretical ItemViewModel
will need to know whether or not a particular Item
is a favorite or not, and we want that actual determination to be handled by some instance of FavoriteStore
. That might be a memory-backed implementation for tests and a real one — saving to a database or Web service — for the real app.
We could go with something like this:
interface FavoriteStore<T> {
fun isFavorite(thingy: T): Boolean
fun toggleFavorite(thingy: T, isFavorite: Boolean)
}
class InMemoryFavoriteStore<T> : FavoriteStore<T> {
private val favorites: MutableMap<T, Boolean> = mutableMapOf()
override fun isFavorite(thingy: T) = favorites[thingy] ?: false
override fun toggleFavorite(thingy: T, isFavorite: Boolean) {
favorites[thingy] = isFavorite
}
}
data class Item(val id: String, val name: String)
class ItemViewModel(val favorites: FavoriteStore<Item>)
fun main() {
val vm = ItemViewModel(InMemoryFavoriteStore())
val item = Item("this is my id", "this is my name")
println("item isFavorite: ${vm.favorites.isFavorite(item)}")
vm.favorites.toggleFavorite(item, true)
println("item isFavorite: ${vm.favorites.isFavorite(item)}")
}
We expose a favorites
property from the ItemViewModel
that is our FavoriteStore
. Consumers of that ItemViewModel
then work with the favorites
object from there.
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.