Skip to content

Instantly share code, notes, and snippets.

@kubode
Last active December 10, 2015 07:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kubode/a30f0383c39d69ad821b to your computer and use it in GitHub Desktop.
Save kubode/a30f0383c39d69ad821b to your computer and use it in GitHub Desktop.
Lambda使いまくったDI
import android.app.Activity
import android.app.Application
import android.app.Service
import android.content.Context
import android.support.v4.app.Fragment
import android.view.View
import java.util.*
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KClass
import kotlin.reflect.KProperty
/**
* Implements this interface if has [ObjectGraph].
*/
public interface HasObjectGraph {
/**
* Define [ObjectGraph].
*/
val objectGraph: ObjectGraph
}
private interface Wrapper<V> {
val value: V
}
private class NormalWrapper<V>(private val objectGraph: ObjectGraph,
private val initializer: (ObjectGraph) -> V) : Wrapper<V> {
override val value: V
get() = initializer(objectGraph)
override fun toString(): String {
return "$value"
}
}
private class SingletonWrapper<V>(private val objectGraph: ObjectGraph,
private val initializer: (ObjectGraph) -> V) : Wrapper<V> {
override val value: V by lazy { initializer(objectGraph) }
override fun toString(): String {
return "$value(Singleton)"
}
}
private enum class Type(private val wrapper: (ObjectGraph, (ObjectGraph) -> Any) -> Wrapper<*>) {
NORMAL({ objectGraph, initializer -> NormalWrapper(objectGraph, initializer) }),
SINGLETON({ objectGraph, initializer -> SingletonWrapper(objectGraph, initializer) });
internal fun wrap(objectGraph: ObjectGraph, initializer: (ObjectGraph) -> Any): Wrapper<*> {
return wrapper(objectGraph, initializer)
}
}
private class Provider<V : Any>(internal val type: Type,
internal val initializer: (ObjectGraph) -> V)
/**
* Define module by extend this class like below.
*
* ```
* class MyModule : Module() {
* init {
* provide(Double::class, { Math.random() })
* provideSingleton(Int::class, { 1 }, "named")
* provideSingleton(String::class, { "Dependency: ${it.get(Int::class}" })
* }
* }
* ```
*/
public abstract class Module {
internal val includes: MutableList<Module> = LinkedList()
@Suppress("EXPOSED_PROPERTY_TYPE")
internal val providers: MutableMap<Class<*>, MutableMap<String?, Provider<*>>> = HashMap()
private fun <V : Any> add(clazz: KClass<V>, name: String? = null, provider: Provider<*>) {
providers.getOrPut(clazz.java, { HashMap() }).put(name, provider)
}
/**
* Include other [Module]
*/
protected fun include(module: Module) {
includes.add(module)
}
/**
* Provide object.
*/
protected fun <V : Any> provide(clazz: KClass<V>, initializer: (ObjectGraph) -> V, name: String? = null) {
add(clazz, name, Provider(Type.NORMAL, initializer))
}
/**
* Provide object as singleton.
*/
protected fun <V : Any> provideSingleton(clazz: KClass<V>, initializer: (ObjectGraph) -> V, name: String? = null) {
add(clazz, name, Provider(Type.SINGLETON, initializer))
}
}
/**
* Inheritable object graph.
*
* @constructor Creates object graph that inherited [parent].
*/
public class ObjectGraph(private val parent: ObjectGraph? = null) {
private val graph: MutableMap<Class<*>, MutableMap<String?, Wrapper<*>>> = HashMap()
/**
* Add new [Module] to this.
*/
public fun add(module: Module): ObjectGraph {
module.includes.forEach { add(it) }
module.providers.forEach {
graph.getOrPut(it.key, { HashMap() }).putAll(it.value.mapValues {
it.value.type.wrap(this, it.value.initializer)
})
}
return this
}
/**
* Get object from this.
* If object not found then throw exception.
*
* @throws RuntimeException When object is not found.
*/
public fun <V : Any> get(clazz: KClass<V>, name: String? = null): V {
@Suppress("UNCHECKED_CAST")
return graph[clazz.java]?.get(name)?.value as V?
?: parent?.get(clazz, name)
?: throw RuntimeException("${clazz.java}(${name ?: "No name"}) is not found.")
}
override fun toString(): String {
return "{parent: $parent, graph: {$graph}}"
}
}
/**
* Find [ObjectGraph].
* If this [Context] implements [HasObjectGraph] then returns `this.objectGraph`.
* If applicationContext implements [HasObjectGraph] then returns `applicationContext.objectGraph`.
* Otherwise throws exception.
*
* @return Founded [ObjectGraph].
* @throws RuntimeException When [ObjectGraph] is not found.
*/
public fun Context.findObjectGraph(): ObjectGraph {
if (this is HasObjectGraph) {
return this.objectGraph
}
val application = this.applicationContext
if (application is HasObjectGraph) {
return application.objectGraph
}
throw RuntimeException("${ObjectGraph::class.java.simpleName} is not found in $this.")
}
private val Application.objectGraph: ObjectGraph by Lazy { it.findObjectGraph() }
private val Activity.objectGraph: ObjectGraph by Lazy { it.application.findObjectGraph() }
private val Fragment.objectGraph: ObjectGraph by Lazy { it.activity.findObjectGraph() }
private val View.objectGraph: ObjectGraph by Lazy { it.context.findObjectGraph() }
private val Service.objectGraph: ObjectGraph by Lazy { it.applicationContext.findObjectGraph() }
public fun <V : Any> Application.inject(clazz: KClass<V>, name: String? = null)
= lazy { objectGraph.get(clazz, name) }
public fun <V : Any> Activity.inject(clazz: KClass<V>, name: String? = null)
= lazy { objectGraph.get(clazz, name) }
public fun <V : Any> Fragment.inject(clazz: KClass<V>, name: String? = null)
= lazy { objectGraph.get(clazz, name) }
public fun <V : Any> View.inject(clazz: KClass<V>, name: String? = null)
= lazy { objectGraph.get(clazz, name) }
public fun <V : Any> Service.inject(clazz: KClass<V>, name: String? = null)
= lazy { objectGraph.get(clazz, name) }
// Like kotlin lazy delegate but the initializer gets the target and metadata passed to it
internal class Lazy<T, V>(private val initializer: (T) -> V) : ReadOnlyProperty<T, V> {
private object EMPTY
@Suppress("UNCHECKED_CAST")
private var value: V = EMPTY as V
override fun getValue(thisRef: T, property: KProperty<*>): V {
if (value == EMPTY) {
value = initializer(thisRef)
}
return value
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment