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 skillfully 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.
- The customer places an order with the waiter.
- When the order is for an entrée, the waiter delegates the entrée preparation to the chef.
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.
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.
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 the code above, the
prepareEntree()
function inWaiter
simply calls the same function on thechef
object, sending along the samename
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 theWaiter
delegates (verb) theprepareEntree()
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 theChef
. 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
andChef
have a function calledprepareEntree()
, and those two functions have the same parameter types and return type.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
andChef
both implement the same interface. Remember, when we do this, we also need to mark theprepareEntree()
function asoverride
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 { fun prepareBeverage(name: String): Beverage? = when (name) { "Water" -> Beverage.WATER "Soda" -> Beverage.SODA else -> null } 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 theChef
, plus we’ve created an interface forprepareEntree()
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
andWaiter
classes to implement the new property and functions. As for how theChef
class might implement those functions… we can leave that to our imaginations. We can easily see how this affects theWaiter
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 theWaiter
to theChef
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.
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:
- Indicate which functions and properties to delegate
- 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 thechef
object, we can simply write this:class Waiter(private val chef: Chef) : KitchenStaff by chef { fun prepareBeverage(name: String): Beverage? = when (name) { /* ... */ } 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 theWaiter
class body, but it’s still delegating them.The only functions left inside this class are
prepareBeverage()
andacceptPayment()
- the two functions thatWaiter
handles himself. In other words, when you look at this class, you can really focus on what’s unique about theWaiter
class instead of having to see everything that it delegates tochef
.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
implementsKitchenStaff
by
delegating tochef
.Or, illustrated:
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.
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 abartender
property to theWaiter
, and manually delegate to it, just like we did previously with thechef
.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 thechef
, and we’re manually delegatingprepareBeverage()
to theBartender
. This is fine, but Kotlin also allows us to useby
to delegate to more than one object at a time, and it works just like you’d expect. Simply addby bartender
afterBarStaff
, 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 theWaiter
class. All of the delegation boilerplate is tucked away behind those simpleby
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.”
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 theprepareEntree()
function inWaiter
again, and manually delegate it to thechef
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 theWaiter
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 thechef
.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 calledreceiveCompliment()
. So, when this function is called on aWaiter
object, it’s sent along to itschef
object. Inside theChef
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 thechef
.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 theBartender
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
andBartender
include a function calledreceiveCompliment()
- and they both have the same parameter types and return type.So, when our code calls
waiter.receiveCompliment()
, what should Kotlin do with it? Should it send the compliment to thechef
object, or to thebartender
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 themessage
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 thechef
andbartender
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 likeChicken
,Pig
, andCow
. 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 theeat()
function directly inPig
, 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 - likeCow
,Chicken
, andPig
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
In this chapter, you learned:
- What delegation is.
- How to manually delegate from one object to another.
- How to use Kotlin’s class delegation to automatically delegate.
- How to override particular functions that would otherwise be delegated.
- How to resolve conflicts when two delegates provide an implementation for the same function.
- How to use delegation for general and specific types.
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!
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). ↩︎