深入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 關鍵字會招致可讀性變差,我們能夠經過介面隔離的方式來處理這個問題
  • 當我們重寫帶有上下文承受者的函式時,必需宣告為相同型別的上下文承受者