中级教程: 带接受者的 Lambda 表达式
在这一章中, 你将学习在另一种函数类型 Lambda 表达式中如何使用接受者, 以及它们如何帮助你创建一个特定领域专用语言(Domain-Specific Language, DSL).
带接受者的 Lambda 表达式
在初学者教程中, 你已经学习了如何使用 Lambda 表达式. Lambda 表达式也可以带有接受者. 这种情况下, Lambda 表达式能够访问接受者的任何成员函数或属性, 而不必每次都明确的指明接受者. 没有了这些额外的引用, 你的代码会变得更加易于阅读和维护.
带接受者的 Lambda 表达式的语法与定义函数类型时不同. 首先, 请写下你想要扩展的接受者. 之后, 是一个 . 号, 之后写下你的函数类型定义的其它部分. 例如:
这个函数类型:
接受者是
MutableList<Int>.括号
()之内没有函数参数.没有返回值:
Unit.
我们来看看下面的实例, 它在画布上绘制图形:
在这个示例中:
Canvas类有 2 个函数, 模拟绘制圆形和正方形.render()函数接受一个block参数, 返回一个Canvas类的实例.block参数是一个带接受者的 Lambda 表达式, 其中Canvas类是接受者.render()函数创建一个Canvas类的实例, 并使用它作为接受者, 在canvas实例上调用block()Lambda 表达式.main()函数调用render()函数, 使用 Lambda 表达式, 传递给block参数.在传递给
render()函数的 Lambda 表达式内, 程序会在Canvas类的实例上调用drawCircle()和drawSquare()函数.由于
drawCircle()和drawSquare()函数是在带接受者的 Lambda 表达式之内调用, 因此可以象在Canvas类之内一样调用它们.
如果你想要创建一个特定领域专用语言(Domain-Specific Language, DSL), 带接受者的 Lambda 表达式会非常有用. 因为你可以访问接受者的成员函数和属性, 而不必明确引用接受者, 你的代码会变得更加精简.
为了演示这一点, 我们来考虑一个配置菜单中项目的示例. 我们从一个 MenuItem 类和一个 Menu 类开始, Menu 类包含一个向菜单中添加项目的函数, 名为 item(), 以及一个包含所有项目的列表, 名为 items:
我们使用一个带接受者的 Lambda 表达式, 将它作为函数参数 (init) 传递给 menu() 函数, 这个函数构建一个菜单, 作为开始点:
现在你可以使用这个 DSL 来配置一个菜单, 并创建一个 printMenu() 函数, 将菜单结构打印输出到控制台:
如你所见, 使用带接受者的 Lambda 表达式 大大的简化了创建你的菜单所需要的代码. Lambda 表达式不仅仅可以用于设置和创建, 也可以用于配置. 它们普遍用于构建 API, UI 框架, 以及配置构建器的 DSL, 以生成精简的代码, 让你能够更容易的专注于底层代码结构和逻辑.
Kotlin 的生态环境中存在这种设计模式的很多例子, 例如标准库中的 buildList() 和 buildString() 函数.
实际练习
习题 1
你有一个 fetchData() 函数, 参数是一个带接受者的 Lambda 表达式. 请更新 Lambda 表达式, 使用 append() 函数, 让你的代码的输出成为: Data received - Processed.
习题 2
你有一个 Button 类, 以及 ButtonEvent 和 Position 数据. 请编写代码, 触发 Button 类的 onEvent() 成员函数, 触发一个 double-click 事件. 你的代码应该打印输出 "Double click!".
习题 3
编写一个函数, 创建一个整数 List 的副本, 其中每个元素增大 1. 请使用已经提供的函数框架, 它使用一个 incremented 函数扩展了 List<Int>.