Kotlin 之 DSL 篇二

参考:

  1. kotlin 实战

1. 使用 invoke 约定构建更灵活的代码块嵌套

invoke 约定允许把自定义类型的对象当做函数一样调用;比如:函数类对象;kotlin中,使用invoke约定也可以支持自己的对象;但此功能比较费解;

1.1 使用 invoke约定:类似函数一样的调用的对象

之前,我们提到过操作符重载,如:[];对应get set 方法

类如果定义了使用operator的 invoke方法,就可以被当做函数一样调用;

class Greeter(val greeting: String) {
    operator fun invoke(name: String) {  // operator invoke
        println("$greeting , $name")
    }
}

fun main(args: Array<String>) {
    // 允许将test实例当做函数调用
    val test = Getter("Good Lock ")
    println(test("better"))
}

1.2 invoke 约定和函数式类型

回顾:
lambda 除去内联的,都是被编译成实现了函数式接口(FunctionN)的类型,这些接口具有对象数量参数的invoke方法;

当lambda作为函数调用时,这种操作被转换成一次invoke方法调用;将复杂的lambda拆成多个方法,仍然允许将他与接收函数类型参数的函数一起使用;

如下例子(使用复杂的条件来过滤):

data class Issue(
        val id: String, val project: String, val type: String,
        val priority: String, val description: String
)

class ImportantIssuesPredicate(val project: String) : (Issue) -> Boolean {
    // 拆分多个方法
    override fun invoke(issue: Issue): Boolean {
        return issue.project == project && issue.isImportant()
    }
    private fun Issue.isImportant(): Boolean {
        return type == "Bug" && (priority == "Major" || priority == "Critical")
    }
}

fun main(args: Array<String>) {
    val i1 = Issue("Android-1234", "Android", "Bug",  "Major", "Login fail")
    val i2 = Issue("iOS-1234", "iOS", "Bug",  "Major", "Login fail")
    val p = ImportantIssuesPredicate("Android")

    for(i in listOf(i1,i2).filter(p)) {  // p invoke
        println(i.id)
    }
}

如果要在一个lambda中,放入逻辑判断,就比较复杂,这里拆分了多个方法,使各个方法职责明确;
将lambda转换成一个实现了函数类型接口的类,并重写接口的invoke方法

直接使用lambda 如下:

for(i in listOf(i1,i2).filter { it.project == "Android" && it.type == "Bug"
       && (it.priority == "Major" || it.priority == "Critical")
    }) {
        println(i.id)
    }

1.3 invoke 约定:在Gradle中声明依赖

如下代码:

dependencies.compile("junit:junit:4.11")
dependencies {
    compile("junit:junit:4.11")
}

2种方式添加依赖,实际上,第二种方式,调用的是 invoke方法,然后接受一个 lambda的参数;

class DenpenderyHandler {
    fun compile(coordinate: String) {
        println("add dependency on $coordinate")
    }
    operator fun invoke(body: DenpenderyHandler.() -> Unit) {
        body()
    }
}

fun main(args: Array<String>) {
    val dep = DenpenderyHandler()
    dep.compile("com.git.basenet:basenet:0.0.7")
    dep {
        compile("com.git.basenet:basenet:0.0.7")
    }
}

2. 实战中的 kotlin dsl

kotlin相关dsl的知识点,全部介绍完毕,带接收者的lambda(在我们的分享上,有一个哥们提出 扩展匿名函数)为dsl中的重点;

那么DSL能够干什么呢?后续我们再实战中,一一来过,典型的包括:测试、Android UI 构建(anko库)

2. 1 配合中缀调用

实现 "kotlin" should startWith("ko")

interface Matcher<T> {
    fun test(value: T)
}
// 注意 startWith 这里是个类,而不是方法
class startWith(val prefix: String) : Matcher<String> {
    override fun test(value: String) {
        if(!value.startsWith(prefix)) {
            throw AssertionError("String $value does not start with $prefix")
        }
    }
}
// 中缀
infix fun <T> T.should(matcher: Matcher<T>) = matcher.test(this)

实现 "kotlin" should start with "ko"

// 一个单利,DSL文法的一部分,用来过渡方法调用而已,没其他作用
object start
// 定义with中缀函数
class StartWrapper(val value: String) {
    infix fun with(prefix: String) {
        if (!value.startsWith(prefix)) {
            throw AssertionError("String $value does not start with $prefix")
        }
    }
}
infix fun String.should(x: start): StartWrapper = StartWrapper(this)

注意:上面的start类,是DSL文法的一部分;中缀调用和object实例的结合能够为DSL构建相当复杂的文法,用起来很清晰,写起来,感觉挺费解的
我们也可以实现 end with 啥的,好吧,来写一个;

2. 2 基本数据类型定义扩展:处理日期

实现:1.days.ago,需要Java 8

val Int.days : Period
    get() = Period.ofDays(this)         // this 表示数字字面值

val Period.ago: LocalDate
    get() = LocalDate.now() - this      // 运算符重载 LocalDate.minus

val Period.fromNow: LocalDate
    get() = LocalDate.now() + this      // 运算符重载 LocalDate.plus

对应github: https://github.com/yole/kxdate

2. 3 Anko: 动态创建Android UI

3. 后续安排

整个Kotlin的基础分享,除了 协程外,都完毕了;
后续我们从框架开始入手:

  1. 当然是 Android Anko
  2. json : JKid、Klaxon
  3. http client: Fuel
  4. 响应式: rxKotlin;
  5. web : Kara, Ktor
  6. 桌面: JavaFx
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容