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("张三"))
}
为此,我们需要改造类 InjectRepository
的 get()
函数,使其支持参数注入。
/**
* 获取某类对应的实例化规则并执行该规则以返回所需对象
*/
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>()
}