1. 从 by lazy 开始

委托是一种软件设计模式,Kotlin 通过关键字 by 实现委托模式。常用的延迟初始化 by lazy 就是一种委托,如下所示:

class KD_1 {
    val name: String by lazy { "张三" }
}

使用 by lazy 来实现延迟初始化,实际上是委托了 lazy 这个函数来帮我们来延迟初始化。接下来我们就先从实现一个 MyLazy 开始一步一步的实现依赖注入。MyLazy 代码如下所示:

class MyLazy(init: () -> String) {
    private val value: String = init()
    operator fun getValue(ref: Any, property: KProperty<*>) = value
}

class KD_2 {
    val name: String by MyLazy { "张三" }
}

fun main() {
    val kd2 = KD_2()
    println("KD_2: name = ${kd2.name}")
}

这样就实现了一个自己的委托类 MyLazy,虽然此时 MyLazy 并不能实现延迟初始化,功能与名字格外不搭,但我们的目的是实现依赖注入,请忽略这个细节。 当前的 MyLazy 只能为 String 类型的变量赋值,接下来使用泛型对 MyLazy 进行改造,代码如下:

class MyLazy<out T>(init: () -> T) {
    private val value: T = init()
    operator fun getValue(ref: Any, property: KProperty<*>) = value
}

class KD_3 {
    val name: String by MyLazy { "张三" }
    val age: Int by MyLazy { 18 }
}

fun main() {
    val kd3 = KD_3()
    println("KD_3: name = ${kd3.name} age = ${kd3.age}")
}

2. 提前准备注入规则

上面实现了一个简单的委托类 MyLazy ,其使用方式如下所示:

val user1: User by MyLazy { User("张三") }
val user2: User by MyLazy { User("李四") }

Lazy 是为了实现延迟初始化而诞生的,但要实现依赖注入,这样使用 ` MyLazy 的方式实例化对象显然是多次一举。我们实例化 User 类时需写明其实例化方式,实例化多个 User` 对象就需要重复多次。所以要实现依赖注入,就需要提前准备好用于对象实例化的注入规则,并把这些规则保存起来,供注入时使用。

定义类 InjectRepository 用来保存依赖注入的规则,如下所示:

/** 一个空参数,返回值类型为 T 的函数设置别名为 Function<T> */
typealias Function<T> = () -> T

/**
 * 用于保存依赖注入的规则
 */
class InjectRepository {
    /**
     * 使用 HashMap 来保存用于实例化对象的规则
     */
    val value = hashMapOf<String, Function<*>>()

    /**
     * 通过 addInject() 以 <className, Function> 的格式保存实例化规则
     */
    inline fun <reified T> addInject(noinline cls: Function<T>) {
        val claName = T::class.java.toString()
        value[claName] = cls
    }

    /**
     * 获取某类对应的实例化规则
     */
    fun get(cls: String) : Function<*> {
        return value[cls] ?: throw Exception("inject error")
    }
    
}

接下来定义类 KDApplication 用于实例化一个 InjectRepository 单例,如下所示:

/**
 * 依赖注入主类
 */
object KDApplication {

    val injectRepository = InjectRepository()

    fun start(init: (InjectRepository) -> Unit) {
        init(injectRepository)
    }

    /**
     * 获取某类对应的实例化规则
     */
    fun instance(cls: String) = injectRepository.get(cls)

}

这些就可以通过形如 KDApplication.start { injectRepository -> } 的形式来定义类的实例化规则,具体示例如下所示:

/**
 * 手机类
 */
data class MobilePhone(val name: String)

/**
 * 用户类
 */
data class User(val name: String)


fun main() {
    KDApplication.start {
        it.addInject { User("张三") }
        it.addInject { MobilePhone("小米手机") }
    }
}

3. 注入对象

前面已经把注入规则存储起来了,在依赖注入时只需要从提前准备好的规则中获取规则并执行即可。定义类 Inject 用于实现代理,如下所示:

/**
 * 注入代理
 */
class Inject {
    /**
     * 通过调用 KDApplication.getValue() 函数来寻找对应的实例化规则并执行
     */
    inline operator fun <reified T> getValue(ref: Any, property: KProperty<*>) = run {
        (KDApplication.instance(T::class.java.toString()) as Function<T>).invoke()
    }
}

现在已经可以使用 by Inject() 来注入对象了,使用方式如下所示:

/**
 * 通过 by Inject() 实现注入
 */
class KD_4 {
    val user: User by Inject()
    val mobilePhone: MobilePhone by Inject()
}

fun main() {
    // 提前准备好注入规则
    KDApplication.start {
        it.addInject { User("张三") }
        it.addInject { MobilePhone("小米手机") }
    }

    // 实现对象注入
    val kd4 = KD_4()
    println("userName = ${kd4.user.name}, mobilePhone = ${kd4.mobilePhone.name}")
}

运行程序将会打印如下内容:

userName = 张三, mobilePhone = 小米手机

4. 注入参数

前面实现了对象的注入,但我们经常会遇到一个对象实例化时需要另一个对象作为参数。如下所示,在实例化类 MobilePhone 时需要传入一个 User 对象以表明该手机的所有者是哪个用户。

class KD_5 {
    val mobilePhone: MobilePhone = MobilePhone("小米手机", User("张三"))
}

为此,我们需要改造类 InjectRepositoryget() 函数,使其支持参数注入。

/**
 * 获取某类对应的实例化规则并执行该规则以返回所需对象
 */
inline fun <reified T> get() : T {
    return value[T::class.java.toString()]?.let {
        (it as Function<T>).invoke()
    } ?: throw Exception("inject error")
}

同时改造类 Inject 和类 KDApplication,如下所示:

**
 * 注入代理
 */
class Inject {
    /**
     * 通过调用 KDApplication.getValue() 函数来寻找对应的实例化规则并执行
     */
    inline operator fun <reified T> getValue(ref: Any, property: KProperty<*>) = KDApplication.instance<T>()
}

/**
 * 依赖注入主类
 */
object KDApplication {

    val injectRepository = InjectRepository()

    fun start(init: KDInit) {
        init.invoke(injectRepository)
    }

    /**
     * 获取某类对应的实例化规则
     */
    inline fun <reified T> instance() = injectRepository.get<T>()

}

现在就可以对对象参数进行注入了,使用方式如下所示:

/**
 * 手机类
 */
data class MobilePhone(val name: String, val owner: User)

/**
 * 用户类
 */
data class User(val name: String)


class KD_5 {
    val mobilePhone: MobilePhone by Inject()
}


fun main() {
    // 提前准备好注入规则
    KDApplication.start {
        it.addInject { User("张三") }
        it.addInject { MobilePhone("小米手机", it.get()) }
    }

    // 实现对象注入
    val kd5 = KD_5()
    println("mobilePhone = ${kd5.mobilePhone.name}, owner = ${kd5.mobilePhone.owner.name}")
}

运行程序将会打印如下内容:

mobilePhone = 小米手机, owner = 张三

5. this 关键字

前面我们通过 KDApplication.start { it-> } 的形式来准备注入规则,你是不是也认为使用 it. 特别麻烦?我们可以对类 KDApplication 进行改造,如下所示:

/**
 * 将 参数与扩展对象一致,无返回值 的函数的别名设置为 KDInit
 */
typealias KDInit = InjectRepository.() -> Unit

/**
 * 依赖注入主类
 */
object KDApplication {

    val injectRepository = InjectRepository()

    fun start(init: KDInit) {
        init.invoke(injectRepository)
    }

    /**
     * 获取某类对应的实例化规则
     */
    fun instance(cls: String) = injectRepository.get(cls)
    
}

这样就可以使用 this 关键字来初始化注入规则了。两种方式的对比如下所示:

// 使用 this
KDApplication.start {
    addInject { User("张三") }
    addInject { MobilePhone("小米手机", get()) }
}

// 使用 it
KDApplication.start {
    it.addInject { User("张三") }
    it.addInject { MobilePhone("小米手机", it.get()) }
}

6. 单例

使用注入实现单例对象的创建,为此我们需要类 SingleInstance 来保存单例对象,如下所示:

/**
 * 用于保存单例注入规则及单例对象
 */
class SingleInstance<T>(private val cls: Function<T>) {

    private var value: T? = null

    private fun isCreated(): Boolean = (value != null)

    fun get(): T {
        if (!isCreated()) {
            value = cls.invoke()
        }
        return value ?: throw Exception("single instance create error")
    }

}

同时需要改造类 InjectRepository 使其可以存储单例对象,如下所示:

/**
 * 用于保存依赖注入的规则
 */
class InjectRepository {
    /**
     * 使用 HashMap 来保存用于实例化对象的规则
     */
    val value = hashMapOf<String, Function<*>>()

    /**
     * 使用 HashMap 来保存用于单例对象的规则
     */
    val singleValue = hashMapOf<String, SingleInstance<*>>()

    /**
     * 通过 addInject() 以 <className, Function> 的格式保存实例化规则
     */
    inline fun <reified T> addInject(noinline cls: Function<T>) {
        val claName = T::class.java.toString()
        value[claName] = cls
    }

    /**
     * 通过 addSingleInject() 以 <className, SingleInstance> 的格式保存单例规则
     */
    inline fun <reified T> addSingleInject(noinline cls: Function<T>) {
        val claName = T::class.java.toString()
        singleValue[claName] = SingleInstance(cls)
    }

    /**
     * 获取某类对应的实例化规则并执行该规则以返回所需对象
     */
    inline fun <reified T> get() : T {
        val clsName = T::class.java.toString()
        // 先从单例 map 中获取对象,没有再从普通 map 中获取对象创建规则并实例化对象
        return singleValue[clsName]?.let {
            it.get() as T
        } ?: value[clsName]?.let {
            (it as Function<T>).invoke()
        } ?: throw Exception("inject error")
    }

}

现在就可以实现单例对象的注入了,使用方式如下所示:

class KD_6 {
    val mobilePhone1: MobilePhone by Inject()
    val mobilePhone2: MobilePhone by Inject()
}

fun main() {
    // 提前准备好注入规则
    KDApplication.start {
        addInject { User("张三") }
        // 注意单例注入需使用 addSingleInject 函数
        addSingleInject { MobilePhone("小米手机", get()) }
    }

    // 实现对象注入
    val kd6 = KD_6()
    if (kd6.mobilePhone1 === kd6.mobilePhone2) {
        println("is the same object")
    } else {
        println("isn't the same object")
    }
}

运行程序将会打印如下内容:

is the same object

7. 附完整代码

import java.lang.Exception
import kotlin.reflect.KProperty

/**
 * 将 空参数,返回值类型为 T 的函数的别名设置为 Function<T>
 */
typealias Function<T> = () -> T

/**
 * 将 参数与扩展对象一致,无返回值 的函数的别名设置为 KDInit
 */
typealias KDInit = InjectRepository.() -> Unit

/**
 * 用于保存单例注入规则及单例对象
 */
class SingleInstance<T>(private val cls: Function<T>) {

    private var value: T? = null

    private fun isCreated(): Boolean = (value != null)

    fun get(): T {
        if (!isCreated()) {
            value = cls.invoke()
        }
        return value ?: throw Exception("single instance create error")
    }
    
}

/**
 * 用于保存依赖注入的规则
 */
class InjectRepository {
    /**
     * 使用 HashMap 来保存用于实例化对象的规则
     */
    val value = hashMapOf<String, Function<*>>()

    /**
     * 使用 HashMap 来保存用于单例对象的规则
     */
    val singleValue = hashMapOf<String, SingleInstance<*>>()

    /**
     * 通过 addInject() 以 <className, Function> 的格式保存实例化规则
     */
    inline fun <reified T> addInject(noinline cls: Function<T>) {
        val claName = T::class.java.toString()
        value[claName] = cls
    }

    /**
     * 通过 addSingleInject() 以 <className, SingleInstance> 的格式保存单例规则
     */
    inline fun <reified T> addSingleInject(noinline cls: Function<T>) {
        val claName = T::class.java.toString()
        singleValue[claName] = SingleInstance(cls)
    }

    /**
     * 获取某类对应的实例化规则并执行该规则以返回所需对象
     */
    inline fun <reified T> get() : T {
        val clsName = T::class.java.toString()
        // 先从单例 map 中获取对象,没有再从普通 map 中获取对象创建规则并实例化对象
        return singleValue[clsName]?.let {
            it.get() as T
        } ?: value[clsName]?.let {
            (it as Function<T>).invoke()
        } ?: throw Exception("inject error")
    }

}

/**
 * 注入代理
 */
class Inject {
    /**
     * 通过调用 KDApplication.getValue() 函数来寻找对应的实例化规则并执行
     */
    inline operator fun <reified T> getValue(ref: Any, property: KProperty<*>) = KDApplication.instance<T>()
}

/**
 * 依赖注入主类
 */
object KDApplication {

    val injectRepository = InjectRepository()

    fun start(init: KDInit) {
        init.invoke(injectRepository)
    }

    /**
     * 获取某类对应的实例化规则
     */
    inline fun <reified T> instance() = injectRepository.get<T>()

}