Kotlin has given us some really killer features. Some are obviously useful (null safety), while others come with a warning, like operator overloading and extension functions. One such ‘handle-with-care’ features is the language support for delegation. It’s easy to grasp, quick to apply and the manual literally takes half a page.
interface Base { fun print() } class BaseImpl(val x: Int) : Base { override fun print() { print(x) } } class Derived(b: Base) : Base by b fun main(args: Array<String>) { val b = BaseImpl(10) Derived(b).print() // prints 10 }
The delegate pattern dips it feet in the murky waters of inheritance: it aims to extend the functionality of a class by delegating the call to a helper object with which it shares an interface. This is all very comparable to an inheritance relationship.
Imagine we have a bunch of Rest controllers and want to have all incoming JSON objects pass through a String sanitiser (never mind that there are better AOP based solutions; you get my drift). We also want to do some extra authentication stuff.
Class EmployeeController @AutoWired constructor(sanitiser: StringSanitizerImpl, authenticator: AuthenticatorImpl) : StringSanitizer by sanitizer, Authenticator by authenticator
The good thing is that authenticator and sanitizer can be full-fledged Spring services, so the delegates can have their own Autowired dependencies, something you could never achieve with plain inheritance, much less with Java’s default methods.
Great! Or is it? It is often said that you should favour composition over inheritance. Quite rightly so: inheritance shouldn’t be misused to pull in helper functionality from classes unless there is a clear IS-A relationship. A Book is a LibraryItem, but our EmployeeController is not really a StringSanitiser or an Authenticator. The only thing we have gained is not having to explicitly type the delegate references in the EmployeeController code — you could argue if that’s really a gain. But far worse is that we need to create interfaces just for the sake of delegation and that we expose the delegates’ API to the outside world, which is not what we want.
So: bad example.
You should use Kotlin delegation if you want to extend the behaviour of classes that you cannot or don’t want to subclass for a number of reasons:
- They are final
- You want to offer a limited or different API, using 98% of the existing code, basically an Adapter of Facade.
- You want to hide the implementation from calling code, so they cannot cast to the superclass.
Suppose you have built a powerful library for creating Excel files that you want to use in various modules. Its API is quite extensive and low-level, i.e. not user-friendly. You also want to offer a pared-down version with fewer features and a simplified API. Subclassing won’t do: while you can override and add methods, the public API will only get larger. Here’s how you could handle it.
interface BasicExcelFacade { fun createCell(row: Int, column: Int): Cell /* imagine the interface has twenty more methods */ } class FancyExcelLibrary() : BasicExcelFacade { fun createColor(color: Colors): Color { return Color(color) } override fun createCell(row: Int, column: Int): Cell = Cell(row, column) } class SimpleExcelLibrary(private val impl: FancyExcelLibrary) : BasicExcelFacade by impl { fun colorCellRed(row: Int, column: Int) { //createCell is automatically delegated to impl, like all the other twenty methods val cell = createCell(row, column) //createColor is not in the BasicExcelFacade, so we invoke the implementation directly cell.color = impl.createColor(Colors.RED) } } class Color(val colors: Colors) class Cell(val row: Int, val col: Int, var color: Color) enum class Colors { RED }
You create a BasicExcelFacade interface with all the FancyExcelLibrary methods you want to expose in the simple API (say 40%) and have FancyExcelLibrary implement it. You create a SimpleExcelLibrary class that takes a FancyExcelLibrary reference on construction and you only need to write code for the extra methods: no boilerplate needed.
There is another example where Kotlin delegates are really useful and that is Spring Crud Repositories. They’re great when working with JPA entities and ORM. You extend an interface, parameterising the Entity class and its primary key class (usually a Long or a String). This is all it takes.
interface EmployeeDao : CrudRepository<Employee, Long>
You can then autowire the EmployeeDAO into your code. The actual implementation is opaque and instantiated by the container: you cannot and should not subclass them, but sometimes it’d be darn useful to tweak its behaviour. For instance, I found myself writing code like the following in most of the services that use the generated DAOs.
fun findByIdStrict(id: Long) = employeeDao.findOne(id) ?: throw NotFoundException(“no Employee with id ${id}”)
All I wanted was to throw an exception when an entity can’t be found, and utilise Kotlin null safety in the return type of the findOne method, not having to handle ugly NullPointers
So we can’t subclass the generated DAO, but we can sure delegate to it.
@Service class ActorRepository @Autowired constructor(val actorDao: ActorDao) : ActorDao by actorDao { fun removeById(id: Long) = delete(findById(id)) fun findById(id: Long): Actor = findOne(id) ?: throw NotFoundException("No actor with id $id") override fun findByNameAndAccount(name: String, account: String): Actor = actorDao.findByNameAndAccount(name, account) ?: throw NotFoundException("Actor name unknown $name") }
There is zero code duplication, an no boilerplate! This is actual code from my open source project kwinsie, by the way. Every wondered what it would look like in plain old Java? Just load the generated class file and decompile it.
That’s a heck of a lot of boilerplate you never have to see again… Happy Kotlin coding!
@NotNull private final ActorDao actorDao; public final void removeById(long id) { this.delete(this.findById(id)); } @NotNull public final Actor findById(long id) { Actor var10000 = this.findOne(id); if (var10000 != null) { return var10000; } else { throw (Throwable)(new NotFoundException("No actor with id " + id)); } } @NotNull public Actor findByNameAndAccount(@NotNull String name, @NotNull String account) { Intrinsics.checkParameterIsNotNull(name, "name"); Intrinsics.checkParameterIsNotNull(account, "account"); Actor var10000 = this.actorDao.findByNameAndAccount(name, account); if (var10000 != null) { return var10000; } else { throw (Throwable)(new NotFoundException("Actor name unknown " + name)); } } @NotNull public final ActorDao getActorDao() { return this.actorDao; } @Autowired public ActorRepository(@NotNull ActorDao actorDao) { Intrinsics.checkParameterIsNotNull(actorDao, "actorDao"); super(); this.actorDao = actorDao; } public long count() { return this.actorDao.count(); } public void delete(Long p0) { this.actorDao.delete((Serializable)p0); }