Wednesday, August 21, 2019

Kotlin in Action: Answers to Chapter 10 Exercises

Here's the answers to the chapter 10 exercise.

Exercise: Basic Dependency Injection

So here's the implementation of the build method:
val objMap = mutableMapOf<KClass<*>, Any>()

inline fun <reified T: Any> build() =
  build(T::class)

@Suppress("UNCHECKED_CAST")
fun <T: Any> build(klass: KClass<T>): T {
  if (objMap.containsKey(klass)) {
    return objMap[klass] as T
  } else {
    with(klass.constructors.first()) {
      val paramObjs = parameters
        .map { build(it.type.classifier as KClass<*>) }
        .toTypedArray()
      val obj = call(*paramObjs)
      objMap[klass] = obj
      return obj
    }
  }
}
For the bonus exercise of adding a @Singleton annotation, we'd want to define the annotation like this:
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class Singleton
And then rework the build method like this:
@Suppress("UNCHECKED_CAST")
fun <T: Any> build(klass: KClass<T>): T {
  if (objMap.containsKey(klass)) {
    return objMap[klass] as T
  } else {
    with(klass.constructors.first()) {
      val paramObjs = parameters
        .map { build(it.type.classifier as KClass<*>) }
        .toTypedArray()
      val obj = call(*paramObjs)
      if (klass.findAnnotation<Singleton>() != null) {
        objMap[klass] = obj
      }
      return obj
    }
  }
}
With this code in place, if you run our example with Nodes 1 through 4, you'll notice that the Node1 ids are different when it doesn't have the @Singleton annotation, but they are the same if you annotate the Node1 class with @Singleton.