Kotlin Delegation Using "by"
Kotlin can manage calls to delegate properties using the "by" keyword, reducing boiler plate code and protecting from changes to API changes.
Kotlin has a lot of great language features built in as first class citizens. You can read about them in books such as Kotlin in Action and think to yourself “that's cool”, but until you use them yourself in your own project you don't really appreciate how useful they are.
This post describes the by
keyword and how it can be used to remove a lot of the boilerplate code from using the delegation pattern. The Kotlin compiler effectively creates methods in the underlying byte code to bind calls to the object to which the class is delegating. This can protect code from changes to the underlying interfaces, and can make the code a lot more concise. First let me introduce a common problem that can be solved using this approach.
Multiple Inheritance
In my case I have a class that needs to inherit from multiple interfaces. Each interface already has a default implementation that will suffice in the majority of cases. The code is providing a framework from which to build different games, with common aspects being handled by the framework. Let's look at some of the classes and interfaces that might make up a game.
The Game
interface is shared by all games and has functions to determine if the game is over and who the winner is. Some games are turn based, but not all, so there is a second interface representing a TurnBasedGame
, which keeps track of who is next or allows the turn to be moved on. Both of these use the generic Player
that will need to be provided by the game's implementer.
A common aspect of many games is to use some form of randomness. The CardGame
interface provides a randomised deck of cards from which to draw cards. A DieGame
interface gives access to a roll of the dice.
There are many more interfaces possible that may be shared and combined in many configurations and by different games. Their implementation will be common in a lot of cases. The problem is to provide default implementations for each of these interfaces so that game makers can get access to these common implementations. For example. a DieGame
, which is genericized to allow different type of die faces, might have an implementation that represents a six sided die with dots for the numbers one to six as shown in the class diagram as SixSidedStandardDieGame
.
A common choice would be to build the implementation details into an abstract class. The concrete game implementation would then extend the base implementations and gain access to inherited functions and/or state. This is not possible when there are many different classes to extend as Kotlin (and JVM languages in general) do not allow multiple inheritance.
Delegation
The standard pattern to get around this is to include a member property in game implementation that can do the job in place of implementing the interface function. Each function that needs to be implemented by the game then delegates the task to the property implementing the required interface. The class diagram might look like this for a game of Snakes'n'Ladders, which is a Game
, a TurnBasedGame
and a SixSidedStandardDieGame
.
Let's look at what the code for this might look like. Each function that is implemented by SnakesNLaddersGame
calls the relevant function on the implementing property, one for each interface called game
, die
and turnBasedGame
. A player is represented with a simple class, SnLPlayer
, with just a name property for this demonstration.
data class SnLPlayer(val name: String)
class SnakesNLadders : DiceGame<Int>, TurnBasedGame<SnLPlayer>, Game<SnLPlayer> {
val game = StandardGame<SnLPlayer>()
val die = SixSidedStandardDieGame()
val turnBasedGame = TwoPlayerGame<SnLPlayer>()
override fun isGameOver(): Boolean = game.isGameOver()
override fun winner(): SnLPlayer = game.winner()
override fun whosTurn(): SnLPlayer = turnBasedGame.whosTurn()
override fun changeTurn() = turnBasedGame.changeTurn()
override fun nextRoll(): Int = die.nextRoll()
}
This has generated a lot of boiler plate code, where all that is happening is to call the function on the property to which the task has been delegated. With more complicated games, and as the number of interfaces required grows, this could result in a lot of boiler plate code like this.
Closed Classes
Another problem to consider with the above approach, is that when an interface changes, such as adding a new function or changing the signature slightly, this will force all implementing classes to be updated. Since the core features of these interfaces will be provided with implementations, such as those shown in the class diagram above, it seems disappointing that an interface change would force games that are composed of those classes to need code changes also.
Kotlin classes are closed by default – you cannot extend a closed class to add your own functionality or override behaviour. The reasons, as stated in Kotlin in Action are that a subsequent change to the superclass may change behaviour upon which you are relying on and break your code when you upgrade to a new version.
Placing the open
modifier on a class allows other classes to extend them. This is supposed to warn a code maintainer that there are probably going to be classes that extend from this class, if not in the module where the code resides, then elsewhere. Someone that is using the module will inevitably have extended from the class. This places a duty of care on the code maintainer to be careful when making any updates to an open
class.
It is also another reason for using the delegation pattern. Rather than extend a class, the delegation pattern has been built into Kotlin to allow the use of a helper class to implement functions from an interface. It does this with the by
keyword.
Implemented by
The solution to both multiple inheritance, and getting around closed classes which you cannot change to open
, is that Kotlin can take care of all the calls to the delegated objects for you. All you need to do is to provide an implementation object and let Kotlin know that it is the delegate for an interface using the keyword by
.
In the example above, the code can be reduced to a few lines of code as follows:
class SnakesNLadders2(
game: Game<SnLPlayer> = StandardGame<SnLPlayer>(),
die: DiceGame<Int> = SixSidedStandardDieGame(),
turnBasedGame: TurnBasedGame<SnLPlayer> = TwoPlayerGame<SnLPlayer>()
) : DiceGame<Int> by die,
TurnBasedGame<SnLPlayer> by turnBasedGame,
Game<SnLPlayer> by game {
// rest of the implementation
}
Note that the primary constructor for the class now includes the delegate properties, but that each is given a default implementation. Each interface that is being implemented is followed by the by
keyword and the name of the property to delegate calls to.
Conclusion
Using the delegation pattern to handle multiple inheritance works well, but it leaves the implementation exposed to subsequent changes in the interfaces and adds a lot of boiler plate code.
Kotlin supports the delegation pattern with the by
keyword. As usual with Kotlin, the code is more concise. Using by
will allow a class to withstand changes in the interfaces it relies upon without needing to update the code.