Kotlin 函数式编程和设计模式(学习笔记六)

  • Post author:
  • Post category:其他

六、设计模式

    1、函数式的对象——invoke约定

Kotlin的约定有很多种,而比如使用便捷的get操作,以及重载运算符等等,invoke约定也仅仅是一种约定而已;我们可以把lambda表达式或者函数直接  保存在一个变量中,然后就像执行函数一样直接执行这个变量,这样的变量通常声明的时候都被我们赋值了已经直接定义好的lambda,或者通过成员引用而获取到的函数;但是别忘了,在面向对象编程中,一个对象在通常情况下都有自己对应的类,那我们能不能定义一个类,然后通过构造方法来产生一个对象,然后直接执行它呢?这正是invoke约定发挥作用的地方。

class A(val str: String) {
    operator fun invoke() {
        println(str)
    }
}
fun main(args: Array<String>) {
    val a = A("Hello")
    a()
}
输出:Hello

我们只需要在一个类中使用operator来修饰invoke函数,这样的类的对象就可以直接像一个保存lambda表达式的变量一样直接调用,而调用后执行的函数就是invoke函数。

2、策略模式就是将不同的行为策略进行独立封装,与类在逻辑上解耦。即不同的策略类实现不同的接口,在调用策略的时候传不同的类对象进去即可。可以用高阶函数简化策略模式,把策略类改成策略方法,作为参数传到高阶函数。

3、责任链模式就是避免请求的接收者和发送者质检的耦合关系,将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理完为止。

七、函数式编程

函数式编程是关于不变性和函数组合的编程范式。特征如下:

  • 一等函数支持:函数也是一种数据类型,可以作为参数传入另一个函数中,同时函数也可以返回一个函数。

  • 纯函数和不变性:纯函数指的是没有副作用的函数(不去改变外部数据状态)。函数式编程是转换数据而不是改变数据。

  • 函数的组合:在面向对象编程中是通过对象之间发送消息来构建程序逻辑的;在函数式编程中是通过不同函数组合来构建程序逻辑的。

1. 高阶函数

高阶函数是将函数用作参数或者返回值的函数。Kotlin的Collection类型中有大量的这种高阶函数,例如:Iterable的filter函数

其中predicate: (T) -> Boolean定义了一个函数变量,变量名是predicate,类型是(T) -> Boolean这样一个函数。这个函数表示输入参数是T类型,输出Boolean类型。那我们来定义这样一个函数,然后调用高阶函数Iterable.filter。

fun isLargeThanFive(x: Int): Boolean = x > 5
fun main(args: Array<String>) {
     val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8)
     println(numbers.filter(::isLargeThanFive))  //打印[6, 7, 8]
}

注意:我们使用::来引用一个函数。

2. 匿名函数

匿名函数,其实就是没有函数名的函数。我们也可以使用匿名函数来实现上面的代码:

fun main(args: Array<String>) {
     val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8)
     println(numbers.filter(
               fun (x: Int):Boolean {
                    return x > 5
               }
          )
     )
     //or 写成一行
     //println(numbers.filter(fun(x: Int) = x > 5))
}

3. Lambda表达式

我们也可以使用更简单Lambda表达式来实现:

 val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8)
    numbers.filter({ it > 5 })
    //or 这样
    numbers.filter { it > 5 }

Lambda表达式是这样定义的:

  • lambda 表达式总是括在花括号中。

  • 其参数(如果有的话)在 -> 之前声明(参数类型可以省略)。

  • 函数体(如果存在的话)在 -> 后面。

  • 如果lambda表达式是该调用的唯一函数,则调用中的圆括号可以省略。

  • 如果函数的最后一个参数是一个函数,并且你传递一个 lambda 表达式作为相应的参数,你可以在圆括号之外指定它。

对于最后一点,我们可以看看Iterable.elementAtOrElse方法:

这个方法是返回第index元素,如果不存在的话返回(Int) -> T的函数值,函数参数是index,返回T类型。我们可以看看如何调用:

 val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8)
    println(numbers.elementAtOrElse(10, { 100}))
    //或者可以这样。
    println(numbers.elementAtOrElse(10) {100})

我们现在来定义一个两个参数的lambda表达式:

    val max = { x: Int, y: Int -> if (x > y) x else y }
    //下面这句等同于上面这句代码。
    //val max: (Int, Int) -> Int = { x: Int, y: Int -> if (x > y) x else y }
    println(max(1, 2))

更复杂的,lambda表达式还可以返回一个lambda表达式:

 val sum = { x: Int ->  {y:Int -> x+y } }
    println(sum)  //打印(kotlin.Int) -> (kotlin.Int) -> kotlin.Int
    println(sum(2)(1))  //打印3

3.1 it单个参数的隐私名称

当lambda表达式只有一个参数时,那么它的声明可以省略(包括->)。可以用it代表lambda唯一的参数。

 val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8)
    numbers.filter({ it > 5 })
    //or 这样
    numbers.filter { it > 5 }

3.2 下划线用于未使用的变量(1.1版本起)

如果 lambda 表达式的参数未使用,那么可以用下划线取代其名称:

map.forEach { _, value -> println("$value!") }

4. 闭包

Lambda 表达式或者匿名函数,以及局部函数和对象表达式(object declarations)可以访问其 闭包,即在外部作用域中声明的变量。与 Java 不同的是可以修改闭包中捕获的变量:

fun main(args: Array<String>) {
    val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8)
    var sum = 0
    numbers.filter { it > 5 }.forEach { sum += it }
    println(sum)  //打印21
}

5. 带接收者的函数字面值

使用匿名函数的语法,我们可以直接指定函数字面值的接收者类型。

val sum = fun Int.(other: Int): Int = this + other
println(1.sum(1))  打印2

当接收者类型可以从上下文推断时,lambda 表达式可以用作带接收者的函数字面值。

class HTML {
    fun body() {
        println("HTML BODY")
    }
}
fun html(init: HTML.() -> Unit): HTML { // HTML.()中的HTML是接受者类型
    val html = HTML()  // 创建接收者对象
    html.init()        // 将该接收者对象传给该 lambda
    return html
}
fun main(args: Array<String>) {
    html{
        body()
    }  //打印HTML BODY
}

这个特性可以让我们构建DSL语言,类似安卓中build.gradle中的语法。

更多学习

kotlin基础语法(学习笔记一)
kotlin面向对象(学习笔记二)
kotlin面向对象(学习笔记三)
kotlin面向对象(学习笔记四)
kotlin面向对象(学习笔记五)

关注我获取更多知识或者投稿


版权声明:本文为c6E5UlI1N原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。