深入Go底层原理,重写Redis中间件实战【网盘分享】

语言: CN / TW / HK

深入Go底层原理,重写Redis中间件实战【网盘分享】

download:https://www.zxit666.com/4879/

揭秘 Kotlin 1.6.20 重磅功用 Context Receivers

这篇文章我们一同来聊一下 Kotlin 1.6.20 的新功用 Context Receivers,来看看它为我们处理了什么问题。

经过这篇文章将会学习到以下内容:

  • 扩展函数的局限性
  • 什么是 Context Receivers,以及如何运用
  • Context Receivers 处理了什么问题
  • 引入 Context Receivers 会带来新的问题,我们如何处理
  • Context Receivers 应用范围及留意事项

扩展函数的局限性

在 Kotlin 中承受者只能应用在扩展函数或者带承受者 lambda 表达式中, 如下所示。

class Context {
    var density = 0f
}
// 扩展函数
inline fun Context.px2dp(value: Int): Float = value.toFloat() / density
复制代码

承受者是 fun 关键字之后和点之前的类型 Context,这里躲藏了两个学问点。

  • 我们能够像调用内部函数一样,调用扩展函数 px2dp(),通常分离 Kotlin 作用域函数 with , run , apply 等等一同运用。

    with(Context()) { px2dp(100) } 复制代码

  • 在扩展函数内部,我们能够运用 this 关键字,或者躲藏关键字隐式访问内部的成员函数,但是我们不能访问私有成员

扩展函数运用起来很便当,我们能够对系统或者第三方库停止扩展,但是也有局限性。

  • 只能定义一个承受者,因而限制了它的可组合性,假如有多个承受者只能当做参数传送。比方我们调用 px2dp() 办法的同时,往 logcatfile 中写入日志。

    class LogContext { fun logcat(message: Any){} } class FileContext { fun writeFile(message: Any) {} } fun printf(logContext: LogContext, fileContext: FileContext) { with(Context()) { val dp = px2dp(100) logContext.logcat("print ${dp} in logcat") fileContext.writeFile("write ${dp} in file") } } 复制代码

  • 在 Kotlin 中承受者只能应用在扩展函数或者带承受者 lambda 表达式中,却不能在普通函数中运用,失去了灵敏性

Context Receivers 的呈现带来新的可能性,它经过了组合的方式,将多个上下文承受者兼并在一同,灵敏性更高,应用范围更广。

什么是 Context Receivers

Context Receivers 用于表示一个根本约束,即在某些状况下需求在某些范围内才干完成的事情,它愈加的灵敏,能够经过组合的方式,组织上下文,将系统或者第三方类组合在一同,完成更多的功用。

假如想在项目中运用 Context Receivers,需求将 Kotlin 插件晋级到 1.6.20 ,并且在项目中开启才能够运用。

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.6.20'
}
// ......
kotlinOptions {
    freeCompilerArgs = ["-Xcontext-receivers"]
}
复制代码

如何运用 Context Receivers

当我们完成上述配置之后,就能够在项目中运用 Context Receivers,如今我们将上面的案例改造一下。

context(LogContext, FileContext)
fun printf() {
    with(Context()) {
        val dp = px2dp(100)
        logContext.logcat("print ${dp} in logcat")
        fileContext.writeFile("write ${dp} in file")
    }
}
复制代码

我们在 printf() 函数上,运用 context() 关键字,在 context() 关键字括号中,声明上下文接纳者类型的列表,多个类型用逗号分隔。但是列出的类型不允许反复,它们之间不允许有子类型关系。

经过 context() 关键字来限制它的作用范围,在这个函数中,我们能够调用上下文 LogContextFileContext 内部的办法,但是运用的时分,只能经过 Kotlin 作用域函数嵌套来传送多个承受者,或许在将来可能会提供愈加文雅的方式。

with(LogContext()) {
    with(FileContext()) {
        printf("I am DHL")
    }
}
复制代码

引入 Context Receivers 招致可读性问题

假如我们在 LogContextFileContext 中声明了多个相同名字的变量或者函数,我们只能经过 [email protected] 语句来处理这个问题。

context(LogContext, FileContext)
fun printf(message: String) {
    logcat("print message in logcat ${[email protected]}")
    writeFile("write message in file ${[email protected]}")
}
复制代码

正如你所见,在 LogContextFileContext 中都有一个名为 name 的变量,我们只能经过 [email protected] 语句来访问,但是这样会引入一个新的问题,假如有大量的同名的变量或者函数,会招致 this 关键字分散四处都是,形成可读性很差。所以我们能够经过接口隔离的方式,来处理这个问题。

interface LogContextInterface{
    val logContext:LogContext
}
interface FileContextInterface{
    val fileContext:FileContext
}
context(LogContextInterface, FileContextInterface)
fun printf(message: String) {
    logContext.logcat("print message in logcat ${logContext.name}")
    fileContext.writeFile("write message in file ${fileContext.name}")
}
复制代码

经过接口隔离的方式,我们就能够处理 this 关键字招致的可读性差的问题,运用的时分需求实例化接口。

val logContext = object : LogContextInterface {
    override val logContext: LogContext = LogContext()
}
val fileContext = object : FileContextInterface {
    override val fileContext: FileContext = FileContext()
}
with(logContext) {
    with(fileContext) {
        printf("I am DHL")
    }
}
复制代码

Context Receivers 应用范围及留意事项

当我们重写带有上下文承受者的函数时,必需声明为相同类型的上下文承受者。

interface Canvas
interface Shape {
    context(Canvas)
    fun draw()
}
class Circle : Shape {
    context(Canvas)
    override fun draw() {
    }
}
复制代码

我们重写了 draw() 函数,声明的上下文承受者必需是相同的,Context Receivers 不只能够作用在扩展函数、普通函数上,而且还能够作用在类上。

context(LogContextInterface, FileContextInterface)
class LogHelp{
    fun printf(message: String) {
        logContext.logcat("print message in logcat ${logContext.name}")
        fileContext.writeFile("write message in file ${fileContext.name}")
    }
}
复制代码

在类 LogHelp 上运用了 context() 关键字,我们就能够在 LogHelp 范围内恣意的中央运用 LogContext 或者 FileContex

val logHelp = with(logContext) {
    with(fileContext) {
        LogHelp()
    }
}
logHelp.printf("I am DHL")
复制代码

Context Receivers 除了作用在扩展函数、普通函数、类上,还能够作用在属性 gettersetter 以及 lambda 表达式上。

context(View)
val Int.dp get() = this.toFloat().dp
// lambda 表达式
fun save(block: context(LogContextInterface) () -> Unit) {
}
复制代码

最后我们来看一下,来自社区 Context Receivers 理论的案例,扩展 Json 工具类。

fun json(build: JSONObject.() -> Unit) = JSONObject().apply { build() }
context(JSONObject)
infix fun String.by(build: JSONObject.() -> Unit) = put(this, JSONObject().build())
context(JSONObject)
infix fun String.by(value: Any) = put(this, value)
fun main() {
    val json = json {
        "name" by "Kotlin"
        "age" by 10
        "creator" by {
            "name" by "JetBrains"
            "age" by "21"
        }
    }
}
复制代码

总结

  • Context Receivers 提供一个根本的约束,能够在指定范围内,经过组合的方式完成更多的功用
  • Context Receivers 能够作用在扩展函数、普通函数、类、属性 gettersetterlambda 表达式
  • Context Receivers 允许在不需求继承的状况,经过组合的方式,组织上下文,将系统或者第三方类组合在一同,完成更多的功用
  • 经过 context() 关键字声明,在 context() 关键字括号中,声明上下文接纳者类型的列表,多个类型用逗号分隔
  • 假如大量运用 this 关键字会招致可读性变差,我们能够经过接口隔离的方式来处理这个问题
  • 当我们重写带有上下文承受者的函数时,必需声明为相同类型的上下文承受者