Kotlin對整潔文法的支援
正常文法 | 整潔文法 | 用到的功能 |
StringUtil.capitalize(s) | s.capitalize() | 擴充函數 |
1.to(“one”) | 1 to “one” | 中綴調用 |
set.add(2) | set+=1 | 運算符重載 |
map.get(“key”) | map[“key”] | get方法約定 |
file.use({f->f.read() } ) | file.use{it.read()} | 括号外的lambda |
sb.append(“yes”) sb.append(“no”) | with(sb){ append(“yes”) \n append(“no”)} | 帶接收者的lambda |
DSL語言分類及特點
通用程式設計語言: 有一系列足夠完善的能力來解決幾乎所有能被計算機解決的問題
領域特定語言:專注在特定的任務或者領域上,并放棄的與該領域無關的功能 (外部DSL),而領域特定語言分為外部DSL與内部DSL
DSL更趨向聲明式 :語言包括有指令式和聲明式寫法 ,指令式語言描述執行操作所需步驟的确切序列,每個操作實作都被獨立化了,而聲明式描述了想要的結果并将執行細節留給解釋它的引擎,通常讓執行更有效率。
外部DSL語言:
聲明式寫法,很難與通用程式設計語言的宿主應用程式結合起來使用,外部DSL語言自己的文法并不能直接嵌套使用。
内部DSL: 是使用通用程式設計語言編寫程式的一部分,包含了外部DSL聲明式和通用語言的文法優點
DSL 的結構
DSL與普通的api之間并沒有明确的邊界,但是DSL經常會出現一個通常在其他api中不存在的特征:結構或文法
結構:api的前後調用在一個大的結構塊中。中間需要維護調用的上下文資訊
指令式api:前後調用沒有内在的結構,也不需要維護上下文。
比如常見的DSL結構文法:
android{
sourceSets {
main {
java {
srcDir 'src/java' // 指定源碼目錄
}
resources {
srcDir 'src/resources' //資源目錄
}
}
}
defaultconfig{
}
}
建構結構化的API:DSL中帶接收者的lambda
先來看一個表達式裡面的知識點
/**
* 1.高階函數
* 2.block: () -> Unit Lambda表達式
* 3.T類型的擴充函數
* 4.泛型函數
* 5.帶接收者的Lambda block: T.() -> Unit
*/
inline fun <T> T.apply(block: T.() -> Unit): T {
block()
return this
}
普通函數類型如何轉換成擴充函數類型
-
函數參數類型 = 函數類型(builderAction:( StringBuilder)-> Unit),将函數類型轉換為擴充
函數類型
-
将函數類型簽名中的一個參數(類型)移到括号前面,并用一個.将它與其他的( 參
數)類型分隔開。用 StringBuilder.() -> Unit 代替(StringBuilder ) -> Unit
- 這個特殊的類型( StringBuilder )就叫作接收者類型,傳遞給 lambda 的這個類型的值就叫作接收者對象
fun buildString( builderAction:( StringBuilder)-> Unit ): String {
val sb = StringBuilder ()
sb.builderAction()
return sb . toString ()
}
函數類型轉換成擴充函數類型
fun buildString( builderAction:StringBuilder.()-> Unit ): String {
val sb = StringBuilder ()
sb.builderAction()
return sb . toString ()
}
擴充函數類型表達式
String.(Int,Int)->Unit
一個擴充函數類型,接收者類型是 String,兩個參數類型是 Int ,傳回類型是 Unit
當你将一個普通函數類型轉換為擴充函數類型時,其調用方式也發生了變化 。 像調用一個擴充函數那樣調用 lambda,而不是将對象作為參數傳遞給 lambda。在 使用普通 lambda 時,我們使用這樣的文法将一個 StringBuilder 執行個體作為 參 數給它 :builderActi on (sb )。但當 你将它改成帶接收者的 lambda 時,代碼 就變成了 sb .builderAction () 。再次重 申,這裡的 builderAction 并不是 StringBuilder 類的方法,它是一個函數類型的參數,但可以用調用擴充函數一 樣的文法調用它。
使用 invoke約定建構更靈活的代碼塊嵌套
invoke約定允許把自定義類型的對象當做函數一樣調用。(函數類型對象可以作為函數調用),定義invoke需要使用operator 修飾符進行修飾。入參和傳回類型可以是任意受系統支援的類型:
//源自 Functions.kt
public interface Function2<in P1, in P2, out R> : Function<R> {
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2): R
}
invoke約定調用示例:
class Lambda2 {
fun test() {
val function1= object : Function1<Int, Boolean> {
override fun invoke(p1: Int): Boolean {
println(p1)
return true
}
}
//對象 function1。
function1(1)
}
}
注意:
//這句話會被預設映射為
function1.invoke(1)
函數類型對象調用示例
//function1:(Int)->Boolean 函數對象調用
fun test2(function1:(Int)->Boolean){
function1(1)
}
Lambda,除非是内聯的,都是被編譯成實作了函數式接口(Functionl 等)的類,而這些接
口定義了具有對應數量參數的 invoke 方法,如上 function1:(Int)->Boolean編譯成位元組碼後會被系統使用function1函數進行替換。
使用invoke約定定義,可以将lambda函數體中抽取的方法的作用域盡可能的縮小,能夠在不耦合外部邏輯的情況下實作代碼解耦。
例子:
package dsl
data class Issue(val id:Int, val name:String)
class ImportantIssuesPredicate(val name:String):(Issue)->Boolean{
override fun invoke(p1: Issue): Boolean {
return p1.name.equals(name) && p1.isImport()
}
fun Issue.isImport(): Boolean {
return name.equals(name) && id==1000
}
}
fun main() {
val iss1=Issue(1000,"test")
val iss2=Issue(1001,"import")
val importantIssuesPredicate=ImportantIssuesPredicate("test")
for( issu in listOf<Issue>(iss1,iss2).filter(importantIssuesPredicate)){
println(issu.name)
}
}
如上将 (Issue)->Boolean 最為基類。并複寫invoke方法,并且定義了Issue.import( )擴充函數,将對比的代碼抽取到mportantIssuesPredicate 函數類型對象中。如果不使用nvoke 約定,就需要将判斷代碼嵌套到for函數中。
DSL中的"invoke" 約定:在Gradle中的聲明依賴
下面是開發中常見的依賴配置:
//扁平調用結構,隻有一個依賴時可用
dependencies.compileOnly()
//嵌套代碼塊結構,有多個依賴時可用
dependencies {
chinaImplementation "com.reworld.android:unity-sdk:1.0.0"
}
如上: dependencies 對象是 DependencyHandler類型。
package dsl
class DependencyHandler {
fun compile(dependency:String){
println(dependency)
}
// DependencyHandler作為invoke函數入參的接收者類型。函數體中作為隐式接收者存在。
operator fun invoke(body:DependencyHandler.()->Unit){
body()
}
}
fun main() {
val dependencyHandler=DependencyHandler()
dependencyHandler.compile("123")
dependencyHandler{
compile("123")
compile("123")
compile("123")
}
}
看到main()函數中寫法和build.gradle 中腳本寫法一緻。通過這樣設計的好處,有多個依賴就可以使用嵌套結構,一個依賴可以使用扁平調用結構
實踐中Dsl用法
- 中綴調用連結起來:測試架構中的“should”
infix fun T.should(matcher:Matcher) =matcher.test(this)
infix中綴标示,可以不用寫. 進行調用。
kotlintest
- 在基本類型上定義擴充:處理日期
val yesterday =1.days.ago
val tomorrow =1.days.fromNow
kxDate
- 為sql設計的内部Dsl
Exposed架構
- Anko :動态建立Android Ui
總結:
内部Dsl 是一種Api設計模式,借助多個方法調用組成的結構,可以使用這種模式來建構更表意的Api,帶接收者lambda采用嵌套結構重新定義函數體中的方法如何解析。成員擴充函數依然收到容器的限制。與普通擴充函數具有不同的使用的場景。用作參數的帶接收者的lambda,其類型是擴充函數類型,并且這個調用函數在調用lambda時會為它提供一個接收者執行個體。