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’ feature is the language support for delegation. It’s easy to grasp, quick to apply and the manual literally takes half a page .
1interface Base {
2 fun print()
3}
4
5class BaseImpl(val x: Int) : Base {
6 override fun print() { print(x) }
7}
8
9class Derived(b: Base) : Base by b
10
11fun main(args: Array<String>) {
12 val b = BaseImpl(10)
13 Derived(b).print() // prints 10
14}
The delegate pattern dips its 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.
1Class EmployeeController @AutoWired
2 constructor(sanitiser: StringSanitizerImpl,
3 authenticator: AuthenticatorImpl) :
4 StringSanitizer by sanitizer,
5 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, and 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.
1interface BasicExcelFacade {
2 fun createCell(row: Int, column: Int): Cell
3 /* imagine the interface has twenty more methods */
4}
5
6class FancyExcelLibrary() : BasicExcelFacade {
7 fun createColor(color: Colors): Color {
8 return Color(color)
9}
10
11override fun createCell(row: Int, column: Int): Cell = Cell(row, column)
12}
13
14class SimpleExcelLibrary(private val impl: FancyExcelLibrary) : BasicExcelFacade by impl {
15 fun colorCellRed(row: Int, column: Int) {
16 //createCell is automatically delegated to impl, like all the other twenty methods
17 val cell = createCell(row, column)
18 //createColor is not in the BasicExcelFacade, so we invoke the implementation directly
19 cell.color = impl.createColor(Colors.RED)
20 }
21}
22
23class Color(val colors: Colors)
24class Cell(val row: Int, val col: Int, var color: Color)
25enum 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.
1interface 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.
1fun 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.
1@Service
2class ActorRepository @Autowired constructor(val actorDao: ActorDao) : ActorDao by actorDao {
3 fun removeById(id: Long) = delete(findById(id))
4 fun findById(id: Long): Actor = findOne(id) ?: throw NotFoundException("No actor with id $id")
5 override fun findByNameAndAccount(name: String, account: String): Actor = actorDao.findByNameAndAccount(name, account) ?: throw NotFoundException("Actor name unknown $name")
6}
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. Just in case you’re wondering: the Actors are not Akka actors, but theatre actors, from my hobby project kwinsie.com.
That’s a heck of a lot of boilerplate you never have to see again… Happy Kotlin coding!
1@NotNull
2private final ActorDao actorDao;
3
4public final void removeById(long id) {
5this.delete(this.findById(id));
6}
7
8@NotNull
9public final Actor findById(long id) {
10Actor var10000 = this.findOne(id);
11if (var10000 != null) {
12return var10000;
13} else {
14throw (Throwable)(new NotFoundException("No actor with id " + id));
15}
16}
17
18@NotNull
19public Actor findByNameAndAccount(@NotNull String name, @NotNull String account) {
20Intrinsics.checkParameterIsNotNull(name, "name");
21Intrinsics.checkParameterIsNotNull(account, "account");
22Actor var10000 = this.actorDao.findByNameAndAccount(name, account);
23if (var10000 != null) {
24return var10000;
25} else {
26throw (Throwable)(new NotFoundException("Actor name unknown " + name));
27}
28}
29
30@NotNull
31public final ActorDao getActorDao() {
32return this.actorDao;
33}
34
35@Autowired
36public ActorRepository(@NotNull ActorDao actorDao) {
37Intrinsics.checkParameterIsNotNull(actorDao, "actorDao");
38super();
39this.actorDao = actorDao;
40}
41
42public long count() {
43return this.actorDao.count();
44}
45
46public void delete(Long p0) {
47this.actorDao.delete((Serializable)p0);
48}
More articles
fromJasper Sprengers
Your job at codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
More articles in this subject area
Discover exciting further topics and let the codecentric world inspire you.
Gemeinsam bessere Projekte umsetzen.
Wir helfen deinem Unternehmen.
Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.
Hilf uns, noch besser zu werden.
Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.
Blog author
Jasper Sprengers
Do you still have questions? Just send me a message.
Do you still have questions? Just send me a message.