[译]Kotlin珍品 4 - 多是你没见过的函数类型调用方式

翻译说明: 翻译水平有限,文章内可能会出现不许确甚至错误的理解,请多多包涵!欢迎批评和指正.每篇文章会结合本身的理解和例子,但愿对你们学习Kotlin起到一点帮助.android

原文地址: [Kotlin Pearls 4] It’s an Object… It’s a Function… It’s an Invokable 编程

原文做者: Uberto Barbinibash

前言

在我开始去看其余人写的Kotlin代码的时候,我当时在看到下面的class时很是震撼:ide

class ReceiptText(val template: String): (Int) -> String {
  override fun invoke(amount: Int): String =
        template.replace("%", amount.toString())
}
复制代码

什么?一个继承函数类型的类!能够这样玩的么?我当时严重懵逼了!同窗,若是你也有点懵了的话,那我以为看完这篇文章就应该能搞(geng)懂(meng)了.函数式编程

正文

不要懵.函数类型跟Kotlin中其余的类型一个尿性;你能够把它们当参数,变量和其余的用法,因此为毛不能继承它呢?先不过多纠结这个问题了啊,能够继承!可是若是你继承了一个函数类型的话,编译器会强制让你重载一个跟函数类型里面的参数和返回类型相对应的invoke方法.例如函数类型(Int) -> String那么invoke就是invoke(xx:Int) : String函数

这个invoke方法是什么呢?它就是个没有名字的方法,因此咱们在这个类的对象的后面加上括号就能用了(若是有参数在括号里面带上对应类型的参数).学习

在函数式编程中,函数类型被称为箭头类型,由于它们表达了从一种类型到另外一种类型的态射.像泛型同样,它们属于复合类型集合的一部分.ui

好了.目前来讲这个知识量够了.如今让咱们看看能用函数类型作什么了!spa

  • 类型别名翻译

    在这个系列的前面的文章中我都刻意的避免使用这种类型别名的用法,就是为了让这个例子更能给你们加深印象.一般我我的是会常常在个人项目中使用类型别名的.

    typealias FInt2String = (Int) -> String
    
    class InheritFromFunctionTypes(val template: String) : FInt2String {
       override fun invoke(p1: Int): String {
           return template.replace("%", p1.toString())
       }
    
    }
    复制代码

    做为一个例子,假设咱们须要从一个收据邮件里面打印金额信息.而且咱们把文案模版的配置交给用户.

    首先咱们不用函数类型的方式写一个两个参数的方法:

    fun receipt(template: String, amount: Int) = 
        template.replace("%", amount.toString())
    复制代码

    而后像这样用起来:
    val text = receipt(readTemplateFromConf(), amount)

    这样一看咱们不用函数类型的方式也能够实现啊!可是你们想一下,这种方式的实现同时须要传入文字配置范本和金额两个参数.可是在实际场景中咱们只须要在启动的时候获取一次模版,而后在咱们读取金额的时候再作打印操做.这里若是用函数类型的方式,咱们能够轻松的把读取模版和金额的动做分开来作:

    class ReceiptText(val template: String): (Int) -> String {
        override fun invoke(amount: Int): String =
        template.replace("%", amount.toString())
    }
    val receipt = ReceiptText("Thank you for you donation of $%!")//模版
    //作些其余的事情...
    val text = receipt(123) // 金额 
    
    //"Thank you for you donation of $123!"
    复制代码
  • lambda表达式
    上面的例子还有一种解法就是用lambda去让方法返回函数类型的实例.

    fun receiptText(template: String):(Int)->String ={
        amount -> template.replace("%", amount.toString())//经过lambda获得函数类型的实例
    }
    
    val text2 = receiptText("Thank you for you donation of %!")
    
    
    println(text2(2))
    
    //"Thank you for you donation of 2!"
    
    
    复制代码

    比较这两种实现方式,我认为,第一个基于类的,可读性更强而且更适合复杂逻辑的支持.第二个属于高阶函数,更方便可是有可读性有一点差,因此我倾向于仅仅在简单的逻辑下使用.

  • 配合operator的使用
    objects 和 classes均可以有多个operator invoke方法.我发现它在处理密封类时尤为有用.

    fun receiptText(template: String):(Int)->String ={
        amount -> template.replace("%", amount.toString())//经过lambda获得函数类型的实例
    }
    
    sealed class TemplateString
    
    //an object with invoke operator
    object ReceiptTextObj : TemplateString() {
    operator fun invoke(amount: Int): String = receiptText("My receipt for $%")(amount)
    operator fun invoke(template: String, amount: Int): String = receiptText(template)(amount)
    }
    复制代码

    注意,虽然object ReceiptTextObj没有继承咱们的函数类型(Int) -> String,不过它的invoke 方法跟咱们的类型一致.

  • 泛型类型
    方法类型能够像普通类型同样工做,那么泛型类型呢?答案是 能够的啊!

    拿集合泛型举个例子.咱们能够经过把上面三个列子放进一个集合,而后传入不一样的金额参数再去去调用:

    val functions = mutableListOf<(Int) -> String>()
    functions.add(receiptText("TA %"))//lambda获取函数类型实例
    functions.add(ReceiptText("Thank you for $%!"))//继承函数类型的实例类
    functions.add(ReceiptTextObj::invoke)//密封单例类的invoke
    val receipts = functions.mapIndexed{i, f -> f(i+100) }
    //["TA 100", "Thank you for $101!", "My receipt for $102"]
    复制代码

    我很想知道你是如何使用它们的,以及其余可能的用途。请你们在评论区留言或者给我私信.

  • Builder模式
    咱们能够用它在建造者模式中构造一个复杂的对象:

    data class Person(val name: String, val age: Int, val weight: Double)
    val personBuilder: (String) -> (Int) -> (Double) -> Person = 
    { name ->
        { age ->
            { weight ->
                Person(name, age, weight)
            }
        }
    }
    复制代码

    使用起来像这样:

    val frank = personBuilder("Frank")(32)(78.5)
    //Person(name=Frank, age=32, weight=78.5)") val names = listOf("Joe", "Mary", "Bob", "Alice") val people: List<Person> = names .map { personBuilder(it) } //名字 .map { it(nextInt(80)) } //随机年龄 .map { it(nextDouble(100.0)) } //随机体重 //4 个随机 Person 实例 复制代码
  • DSL

    object Console {
        operator fun invoke (block: (String) -> String): Nothing {
            while (true) {
                val l = readLine()
                if (!l.isNullOrBlank())
                    println(block(l))
            }
        }
    }
    复制代码

    上面这段代码的逻辑是一个控制台无限循环的读取输入流,而后把读取内容交给一个函数类型去处理.能够用到一个用户输入问题,而且控制台返回答案的场景,就想这样:

    fun main() {
        println ("Write your command!")
        Console {
            val parts = it.split(' ')
            when (parts[0]) {
                "go" -> "going ${parts[1]}"
                "eat" -> "eating ${parts[1]}"
                "quit" -> throw InterruptedException("Program has been terminated by user")
                else -> "I don't think so..."
            }
        }
    }
    复制代码

结尾

我但愿大家能喜欢这篇文章,若是喜欢的话 请关注原做者或者在掘金关注我并给我点个赞吧.

相关文章
相关标签/搜索