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:

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.