1. Previous: Chapter 12
  2. Next: Chapter 14
Kotlin: An Illustrated Guide • Chapter 13

Introduction to Class Delegation

Chapter cover image

Roger is out for dinner at a restaurant, when the waiter walks up and asks, “Can I start you off with something to drink, sir?”

“I’d like a soda, please,” says Roger.

“One moment, sir.” The waiter walks back to a counter in the kitchen, fills a glass of soda, and sets it down on the table. “Are you ready to order your meal, sir?”

Roger replies, “Yes, I’d like the salmon on rice, please.”

“I’ll get that in for you,” says the waiter. He walks back to the kitchen again. This time, though, he doesn’t prepare the order himself. Instead, he hands it off to the chef, who skillfullly prepares the delicious dinner. Once it’s ready, the chef hands the meal to the waiter, who returns and places it on the table for Roger.

Delegation in Restaurants and Code

As this story shows, sometimes a waiter can fulfill an order without involving the chef - such as when Roger ordered his beverage - but in other cases, such as when Roger ordered an entrée, the waiter has to hand off the order to the chef to fulfill.

Similarly, in Kotlin, sometimes an object is fully capable of fulfilling a request (such as a function call) on its own, and in other cases, it might need to hand off the request to another object.

Whether this happens in real life or in Kotlin, it’s called delegation.

Manual Delegation

Let’s review the relationship between the customer, the waiter, and the chef.

  1. The customer places an order with the waiter.
  2. When the order is for an entrée, the waiter delegates the entrée preparation to the chef.
Illustration: Customers at the table, waiter, and chef. Arrows between: "places order with" and "delegates entrée preparation to". Includes arrow to beverage also?

Notice that customers never interact with the chef directly - they only ever interact with the waiter, who will interact with the chef on the customers’ behalf.

Same as the previous, but shows a line between the customer and the chef with an X over it.

We can model this customer-waiter-chef relationship in Kotlin. To get started, let’s replace the drawings above with boxes, which will roughly convert our illustration into a simple UML class diagram.

UML class diagram. Customer -> Waiter -> Chef. orders from delegates entrees to Customer Waiter + prepareBeverage(name: String): Beverage? + prepareEntree(name: Entree): Entree? + acceptPayment(money: Int) Chef + prepareEntree(name: String): Entree?

Now we’re ready to create classes to represent the waiter and the chef, along with some enum classes for the beverages and entrées.

class Chef {
    fun prepareEntree(name: String): Entree? = when (name) {
        "Tossed Salad"   -> Entree.TOSSED_SALAD
        "Salmon on Rice" -> Entree.SALMON_ON_RICE
        else             -> null
    }
}

class Waiter(private val chef: Chef) {
    // The waiter can prepare a beverage by himself...
    fun prepareBeverage(name: String): Beverage? = when (name) {
        "Water" -> Beverage.WATER
        "Soda"  -> Beverage.SODA
        else    -> null
    }

    // ... but needs the chef to prepare an entree
    fun prepareEntree(name: String): Entree? = chef.prepareEntree(name)

    fun acceptPayment(money: Int) = println("Thank you for paying for your meal")
}

enum class Entree { TOSSED_SALAD, SALMON_ON_RICE }
enum class Beverage { WATER, SODA }

In this code, the prepareEntree() function in Waiter simply calls the same function on the chef object, sending along the same name argument that it received. This is manual delegation.

The word delegate can be either a noun or a verb, with a slight difference in pronunciation. In the listing above, the chef object here is called a delegate (noun), because the Waiter delegates (verb) the prepareEntree() call to it.

Now we’ve got a class for the waiter and the chef. However, we won’t bother creating a class for the customer. Instead, the code in the next listing will act like a customer, calling functions on a Waiter object.

val waiter = Waiter(Chef())

val beverage = waiter.prepareBeverage("Soda")
val entree = waiter.prepareEntree("Salmon on Rice")

Congratulations! You’ve already created a simple, manual delegation relationship between the Waiter and the Chef. As we’ll see in a moment, delegation can be even easier than this!

Before we move on, though, you might have noticed that both Waiter and Chef have a function called prepareEntree(), and those two functions have the same parameter types and return type.

Points out that the two functions have the same signature. class Chef { fun prepareEntree (name: String): Entree? = when (name) { "Caesar Salad" Entree. CAESAR_SALAD "Salmon on Rice" Entree. SALMON_ON_RICE else null } } class Waiter( private val chef : Chef) { The waiter can prepare a beverage by himself fun prepareBeverage (name: String): Beverage? = when (name) { "Water" Beverage. WATER "Soda" Beverage. SODA else null } but needs the chef to prepare an entree fun prepareEntree (name: String): Entree? = chef .prepareEntree(name) fun acceptPayment (money: Int) = println( "Thank you for paying for your meal" ) } same function name, parameters, and return type fun prepareEntree (name: String): Entree? fun prepareEntree (name: String): Entree?

As we saw in the last chapter, when this happens, we can create an interface. Let’s update our code from Listing 13.1 so that Waiter and Chef both implement the same interface. Remember, when we do this, we also need to mark the prepareEntree() function as override in those two classes.

interface KitchenStaff {
    fun prepareEntree(name: String): Entree?
}

class Chef : KitchenStaff {
    override fun prepareEntree(name: String): Entree? = when (name) {
        "Tossed Salad"   -> Entree.TOSSED_SALAD
        "Salmon on Rice" -> Entree.SALMON_ON_RICE
        else             -> null
    }
}

class Waiter(private val chef: Chef) : KitchenStaff {
    // The waiter can prepare beverages by himself...
    fun prepareBeverage(name: String): Beverage? = when (name) {
        "Water" -> Beverage.WATER
        "Soda"  -> Beverage.SODA
        else    -> null
    }

    // ... but needs the chef to pepare the entrees
    override fun prepareEntree(name: String): Entree? = chef.prepareEntree(name)

    fun acceptPayment(money: Int) = println("Thank you for paying for your meal")
}

Great! We’ve now got a delgation relationship between the Waiter and the Chef, plus we’ve created an interface for prepareEntree() function that they both share.

Delegating More Function Calls

Now, writing one function to manually call chef.prepareEntree() isn’t too bad, but there are plenty of other cases when the waiter might delegate to the chef. For example:

  • Getting a list of the chef’s specials for the day
  • Preparing an appetizer
  • Preparing a dessert
  • Sending the customer’s compliments along to the chef

It’s easy to update the interface to include these things:

interface KitchenStaff {
    val specials: List<String>
    fun prepareEntree(name: String): Entree?
    fun prepareAppetizer(name: String): Appetizer?
    fun prepareDessert(name: String): Dessert?
    fun receiveCompliment(message: String)
}

However, we also have to update the Chef and Waiter classes to implement the new property and functions. As for how the Chef class might implement those functions… we can leave that to our imaginations. We can easily see how this affects the Waiter class, though:

class Waiter(private val chef: Chef) : KitchenStaff {
    // These first two functions are the same as before
    fun prepareBeverage(name: String): Beverage? = when (name) {
        "Water" -> Beverage.WATER
        "Soda"  -> Beverage.SODA
        else    -> null
    }

    fun acceptPayment(money: Int) = println("Thank you for paying for your meal")

    // Manually delegating to the chef for all of these things: 
    override val specials: List<String> get() = chef.specials
    override fun prepareEntree(name: String) = chef.prepareEntree(name)
    override fun prepareAppetizer(name: String) = chef.prepareAppetizer(name)
    override fun prepareDessert(name: String) = chef.prepareDessert(name)
    override fun receiveCompliment(message: String) = chef.receiveCompliment(message)
}

As more and more properties and functions are added to the KitchenStaff interface, manually delegating from the Waiter to the Chef becomes tedious to write, tedious to read, and makes it more likely that we could accidentally get something wrong.

When you look at override functions above, you’ll notice a lot of repeated text in the code:

  • The override fun
  • The name of the function on the left and the right side of the line
  • The parameter names on the left and also on the right

The pattern is the same on each line. Code that has the same repeated pattern like this is called boilerplate.1 Even using Kotlin’s expression body functions, as we’re doing here, the boilerplate is really starting to pile up.

Lots of boilerplate override val specials : List<String> get () = chef . specials override fun prepareEntree (name: String) = chef .prepareEntree(name) override fun prepareAppetizer (name: String) = chef .prepareAppetizer(name) override fun prepareDessert (name: String) = chef .prepareDessert(name) override fun receiveCompliment (message: String) = chef .receiveCompliment(message) Lots of boilerplate!

Thankfully, Kotlin makes it easy to do this kind of delegation, without having to write it all by hand!

Easy Delegation, the Kotlin Way

Instead of writing all of the boilerplate for delegation manually, we can just use Kotlin’s class delegation feature. To do this, we just need to do two things:

  1. Indicate which functions and properties to delegate
  2. Indicate which object they should be delegated to

The functions and properties are specified by the name of the interface that contains them, and the delegate is specified using the by keyword. Once you do this, you can remove the manual delegation.

For example, to delegate all of the properties and functions in the KitchenStaff to the chef object, we can simply write this:

class Waiter(private val chef: Chef) : KitchenStaff by chef {
    fun prepareBeverage(name: String): Beverage? = when (name) {
        "Water" -> Beverage.WATER
        "Soda"  -> Beverage.SODA
        else    -> null
    }

    fun acceptPayment(money: Int) = println("Thank you for paying for your meal")
}

Easy, right? This code works the same as Listing 13.5; it’s just written differently. With this change, all of the properties and functions that were manually delegated to chef are completely omitted from the Waiter class body, but it’s still delegating them.

The only functions left inside this class are prepareBeverage() and acceptPayment() - the two functions that Waiter handles himself. In other words, when you look at this class, you can really focus on what’s unique about the Waiter class instead of having to see everything that it delegates to chef.

By simply including by chef, Kotlin does a lot of work for us. When you look at the first line of Listing 13.6, you can read it like:

Waiter implements KitchenStaff by delegating to chef.

Or, illustrated:

Annotated code: `Waiter` implements `KitchenStaff` `by` delegating to the `chef` object. (point to each thing) class Waiter( private val chef : Chef) : KitchenStaff by chef implements delegating to

Multiple Delegates

Good news - the restaurant just opened up its new beverage bar, offering fancy drinks like peach iced tea and tea-lemonade. Instead of preparing the beverages himself, the waiter will now delegate beverage preparation to the bartender.

Shows the waiter delegating to the bartender.

To start with, let’s add a new interface and class for the bartender. We’ll also update the Beverage enum class with the new beverage options.

interface BarStaff {
    fun prepareBeverage(name: String): Beverage?
}

class Bartender: BarStaff {
    override fun prepareBeverage(name: String): Beverage? = when(name) {
        "Water"        -> Beverage.WATER
        "Soda"         -> Beverage.SODA
        "Peach Tea"    -> Beverage.PEACH_ICED_TEA
        "Tea-Lemonade" -> Beverage.TEA_LEMONADE
        else           -> null
    }
}

enum class Beverage { WATER, SODA, PEACH_ICED_TEA, TEA_LEMONADE }

Now, we want the Waiter to delegate the beverage preparation to the bartender. To start, let’s add a bartender property to the Waiter, and manually delegate to it, just like we did previously with the chef.

class Waiter(
    private val chef: Chef, 
    private val bartender: Bartender
) : KitchenStaff by chef, BarStaff {
    override fun prepareBeverage(name: String) = bartender.prepareBeverage(name)
    fun acceptPayment(money: Int) = println("Thank you for paying for your meal")
}

Here, we’ve got Kotlin automatically delegating everything in KitchenStaff to the chef, and we’re manually delegating prepareBeverage() to the Bartender. This is fine, but Kotlin also allows us to use by to delegate to more than one object at a time, and it works just like you’d expect. Simply add by bartender after BarStaff, like this:

class Waiter(
    private val chef: Chef, 
    private val bartender: Bartender
) : KitchenStaff by chef, BarStaff by bartender {
    fun acceptPayment(money: Int) = println("Thank you for paying for your meal")
}

With this, we now have a class that delegates to two different classes. As in Listing 13.8, the body of the Waiter class shows only what’s unique to the Waiter class. All of the delegation boilerplate is tucked away behind those simple by declarations!

Overriding a Delegated Call

As it’s getting later, the restaurant is getting crowded, and the chef is busy trying to prepare food for all of the customers. So, the chef says to the waiter, “The salad is easy to prepare. From now on, if you get an order for a tossed salad, just take care of it yourself. I’ll still handle the fancier meals.”

Waiter preparing salad. Chef preparing other meals.

As we’ve seen, when we use Kotlin’s class delegation, all of the properties and functions from the interface are automatically sent to the designated object. However, you can also choose not to automatically delegate one or more particular properties or functions from the interface.

Let’s update our Kotlin code so that the Waiter can prepare the salad by himself. To do this, we can include the prepareEntree() function in Waiter again, and manually delegate it to the chef only when needed. Here’s how that looks:

class Waiter(
    private val chef: Chef, 
    private val bartender: Bartender
) : KitchenStaff by chef, BarStaff by bartender {
    override fun prepareEntree(name: String): Entree? = 
        if (name == "Tossed Salad") Entree.TOSSED_SALAD else chef.prepareEntree(name)

    fun acceptPayment(money: Int) = println("Thank you for paying for your meal")
}

When a function from KitchenStaff is included in the Waiter class, Kotlin will use that function instead of the sending it to the delegate. However, we can still choose to manually delegate to it, as we’re doing here in Listing 13.10. When the customer orders anything other than a tossed salad, we manually delegate to the chef.

So, we can combine Kotlin’s class delegation with manual delegation.

Managing Conflicts

When the meal is fantastic, sometimes the customer tells the waiter to send along compliments to the chef. The KitchenStaff has a function called receiveCompliment(). So, when this function is called on a Waiter object, it’s sent along to its chef object. Inside the Chef class, we can simply print out that the compliment was received, like this:

interface KitchenStaff {
    // ... disregarding other properties and functions ...
    fun receiveCompliment(message: String)
}

class Chef : KitchenStaff 
    // ... disregarding other properties and functions ...
    override fun receiveCompliment(message: String) =
        println("Chef received a compliment: $message")
}

And of course, calling this function on the waiter will send it along to the chef.

val waiter = Waiter(Chef(), Bartender())

val beverage = waiter.prepareBeverage("Water")
val entree = waiter.prepareEntree("Salmon on Rice")

waiter.receiveCompliment("The salmon entree was fantastic!")

The chef isn’t the only one who might receive a compliment, though. The bartender makes an excellent peach iced tea, and sometimes the customers want to send along their compliments to the bartender, too! Let’s update the BarStaff interface and the Bartender class so that he can also receive a compliment.

interface BarStaff {
    fun prepareBeverage(name: String): Beverage
    fun receiveCompliment(message: String)
}

class Bartender: BarStaff {
    // ... disregarding other properties and functions ...
    override fun receiveCompliment(message: String) =
        println("Bartender received a compliment: $message")
}

Now, both Chef and Bartender include a function called receiveCompliment() - and they both have the same parameter types and return type.

Show how they both have the same thing. interface KitchenStaff { val specials : List<String> fun prepareEntree (name: String): Entree? fun prepareAppetizer (name: String): Appetizer? fun prepareDessert (name: String): Dessert? fun receiveCompliment (message: String) } interface BarStaff { fun prepareBeverage (name: String): Beverage fun receiveCompliment (message: String) } same function name, parameter, and return type

So, when our code calls waiter.receiveCompliment(), what should Kotlin do with it? Should it send the compliment to the chef object, or to the bartender object? Or maybe both?

It’s up to you as the programmer to decide. In fact, in a situation like this, where you try to use class delegation for two interfaces that have the same property or function, you’ll get a compiler error like this:

Class ‘Waiter’ must override public open fun receiveCompliment(message: String): Unit defined in Waiter because it inherits many implementations of it.

To resolve this, we’ll need to include this function in the Waiter class. For example, we could check to see if the message includes the word “entree” or “beverage”, and manually delegate to the chef or bartender accordingly. Then, as a last resort, the waiter can receive the compliment himself.

class Waiter(
    private val chef: Chef, 
    private val bartender: Bartender
) : KitchenStaff by chef, BarStaff by bartender {
    override fun receiveCompliment(message: String) = when {
        message.contains("entree")   -> chef.receiveCompliment(message)
        message.contains("beverage") -> bartender.receiveCompliment(message)
        else                         -> println("Waiter received compliment: $message")
    }

    override fun prepareEntree(name: String): Entree? = 
        if (name == "Tossed Salad") Entree.TOSSED_SALAD else chef.prepareEntree(name)

    fun acceptPayment(money: Int) = println("Thank you for paying for your meal")
}

And now, the customer can send compliments along to the chef, the bartender, or the waiter!

val waiter = Waiter(Chef(), Bartender())

waiter.receiveCompliment("The salmon entree was fantastic!")
waiter.receiveCompliment("The peach tea beverage was fantastic!")
waiter.receiveCompliment("The service was fantastic!")
Chef received a compliment: The salmon entree was fantastic!
Bartender received a compliment: The peach tea beverage was fantastic!
Waiter received compliment: The service was fantastic!

So far, we’ve used class delegation to easily forward property calls and function calls from a waiter object to the chef and bartender objects. Although waiters, chefs, and bartenders illustrate the concept of delegation with concrete examples, delegation is also often used for a different purpose - to share general code across multiple specific types. So before we wrap up this chapter, let’s see how that works!

Delegation for General and Specific Types

As we saw in the last chapter, interfaces often represent general types like FarmAnimal, and classes that implement interfaces often represent more specific types like Chicken, Pig, and Cow. In some cases, you might have some general code that you want to share among many different specific types.

For example, all farm animals will want to eat, although the particular food that they eat might be a little different depending on what kind of animal they are. In this next code listing, we have three farm animals, each with its own eat() function.

class Cow {
    fun eat() = println("Eating grass - munch, munch, munch!")
}

class Chicken {
    fun eat() = println("Eating bugs - munch, munch, munch!")
}

class Pig {
    fun eat() = println("Eating corn - munch, munch, munch!")
}

Cow().eat()     // Eating grass - munch, munch, munch!
Chicken().eat() // Eating bugs - munch, munch, munch!
Pig().eat()     // Eating corn - munch, munch, munch!

You probably noticed some boilerplate code here again. In fact, other than their names, the only difference between each of these classes is the food in the string. The eat() function is general, and we can share it across these specific classes with delegation. It’s quick and easy, so let’s start with a simple interface for someone who eats.

interface Eater {
    fun eat()
}

Let’s implement this interface with a class that makes that munching sound.

class Muncher(private val food: String) : Eater {
    override fun eat() = println("Eating $food - munch, munch, munch!")
}

By using class delegation, we can make it so that all of the animals share this implementation of the eat() function. Notice that there’s a lot less repeated code now:

class Cow : Eater by Muncher("grass")
class Chicken : Eater by Muncher("bugs")
class Pig : Eater by Muncher("corn")

Cow().eat()     // Eating grass - munch, munch, munch!
Chicken().eat() // Eating bugs - munch, munch, munch!
Pig().eat()     // Eating corn - munch, munch, munch!

As it turns out, the pigs on this farm like to scarf down their dinner much faster than the cows and chickens, who usually just graze. So instead of sharing the Mucher code with them, we can just implement the eat() function directly in Pig, like this:

class Cow : Eater by Muncher("grass")
class Chicken : Eater by Muncher("bugs")
class Pig : Eater {
    override fun eat() = println("Scarfing down corn - NOM NOM NOM!!!")
} 

Cow().eat()     // Eating grass - munch, munch, munch!
Chicken().eat() // Eating bugs - munch, munch, munch!
Pig().eat()     // Scarfing down corn - NOM NOM NOM!!!

Alternatively, we can accomplish the same thing by creating another class, and using it as the Pig’s delegate. Here’s how that looks:

class Scarfer(private val food: String) : Eater {
    override fun eat() = println("Scarfing down $food - NOM NOM NOM!!!")
}

class Cow : Eater by Muncher("grass")
class Chicken : Eater by Muncher("bugs")
class Pig : Eater by Scarfer("corn")

So, class delegation can be used to share some general code - such as how to eat() - across different specific types - like Cow, Chicken, and Pig classes.

This is only one way of sharing code across different classes. In the next chapter, we’ll look at abstract and open classes, which can also be used to share code.

Summary

Enjoying this book?
Pick up the Leanpub edition today!

Kotlin: An Illustrated Guide is now available on Leanpub See the book on Leanpub

In this chapter, you learned:

As mentioned above, the next chapter will cover abstract and open classes, which can also be used to share code among multiple classes. See you then!


  1. The term boilerplate comes from the old days of the printed newspaper industry, when publishing syndicates would send local newspapers stories on metal plates that were ready for them to print. Those metal plates looked like the plating used when producing steam boilers. Those stories were also often fluff articles that lacked original content, which is how the term got its meaning. (“boilerplate,” Merriam-Webster.com Dictionary, https://www.merriam-webster.com/dictionary/boilerplate. Accessed 1/23/2023). ↩︎

Share this article:

  • Share on Twitter
  • Share on Facebook
  • Share on Reddit