Skip to content

Instantly share code, notes, and snippets.

@ElianFabian
Last active December 10, 2023 22:17
Show Gist options
  • Save ElianFabian/cdb311cc8b580e3079ae368c1c6b6a04 to your computer and use it in GitHub Desktop.
Save ElianFabian/cdb311cc8b580e3079ae368c1c6b6a04 to your computer and use it in GitHub Desktop.
A basic example of dependency injection with reflection in Kotlin.
import kotlin.reflect.KClass
fun main() {
val baseDependencies = listOf(
Gun(bullets = 50),
Hammer(power = 100),
)
val dependencyClasses = listOf(
Hero::class,
)
val resolvedDependencies = getResolvedDependencies(
baseDependencies = baseDependencies,
dependencyClassesToResolve = dependencyClasses,
)
println(resolvedDependencies)
}
data class Gun(val bullets: Int)
data class Hammer(val power: Int)
data class Friend(val gun: Gun)
data class Hero(
val hammer: Hammer,
val friend: Friend,
)
fun getResolvedDependencies(
baseDependencies: List<Any>,
dependencyClassesToResolve: List<KClass<*>>,
): Map<KClass<*>, Any> {
val resolvedDependencies = mutableMapOf<KClass<*>, Any>()
val pendingDependencies = mutableListOf<KClass<*>>()
for (dependencyClassToResolve in dependencyClassesToResolve) {
pendingDependencies.add(dependencyClassToResolve)
val dependency = resolvedDependencies[dependencyClassToResolve]
?: baseDependencies.firstOrNull { it::class == dependencyClassToResolve }
?: getResolvedDependency(
baseDependencies = baseDependencies,
dependencyClassToResolve = dependencyClassToResolve,
resolvedDependencies = resolvedDependencies,
pendingDependencies = pendingDependencies,
)
?: throw throw IllegalStateException("Could not resolve dependency for class '${dependencyClassToResolve.qualifiedName}'.")
resolvedDependencies.putIfAbsent(dependencyClassToResolve, dependency)
pendingDependencies.remove(dependencyClassToResolve)
}
return resolvedDependencies
}
fun getResolvedDependency(
dependencyClassToResolve: KClass<*>,
baseDependencies: List<Any>,
resolvedDependencies: MutableMap<KClass<*>, Any>,
pendingDependencies: MutableList<KClass<*>>,
): Any? {
if (dependencyClassToResolve !in pendingDependencies) {
pendingDependencies.add(dependencyClassToResolve)
}
val constructors = dependencyClassToResolve.constructors
val primaryConstructor = constructors.firstOrNull()
?: constructors.firstOrNull { it.parameters.isEmpty() }
?: throw IllegalArgumentException("Class '$dependencyClassToResolve' does not have a primary constructor")
val parameters = primaryConstructor.parameters
val constructorArgs = arrayOfNulls<Any>(parameters.size)
for ((index, parameter) in parameters.withIndex()) {
val parameterType = parameter.type.classifier as KClass<*>
val dependency = baseDependencies.firstOrNull { it::class == parameterType }
?: resolvedDependencies[parameterType]
?: run {
if ((parameter.type.classifier as KClass<*>).javaPrimitiveType != null) {
throw IllegalStateException("Could not resolve dependency for parameter '${parameter.name}' of primitive type '${parameterType.qualifiedName}' in class: '${dependencyClassToResolve.qualifiedName}'.")
}
null
} ?: run {
if (parameterType in pendingDependencies) {
if (parameterType == dependencyClassToResolve) {
throw IllegalStateException("Could not resolve recursive dependency for parameter '${parameter.name}' of type '${parameterType.qualifiedName}' in class: '${dependencyClassToResolve.qualifiedName}'.")
}
throw IllegalStateException("Could not resolve circular dependency for parameter '${parameter.name}' of type '${parameterType.qualifiedName}' in class: '${dependencyClassToResolve.qualifiedName}'.")
}
null
}
?: getResolvedDependency(
baseDependencies = baseDependencies,
dependencyClassToResolve = parameterType,
resolvedDependencies = resolvedDependencies,
pendingDependencies = pendingDependencies,
)
?: return null
if (parameterType !in resolvedDependencies && dependency !in baseDependencies) {
resolvedDependencies[parameterType] = dependency
}
constructorArgs[index] = dependency
}
pendingDependencies.remove(dependencyClassToResolve)
return primaryConstructor.call(*constructorArgs)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment