Effective Kotlin中文版
  • ReadMe
  • 前言
  • 第一部分:良好的代码
    • 第一章:安全性
      • 第1条:限制可变性
      • 第2条:最小化变量的作用域
      • 第3条:尽可能消除平台类型
      • 第4条: 不要暴露需要推断的类型
      • 第5条:指明你期望的参数和状态
      • 第 6 条: 优先使用标准错误,而不是自定错误
      • 第7条:当返回结果可能缺失时,优先使 null 或 Failure
      • 第8条:妥善处理空值
      • 第9条: 使用 use 来关闭资源
      • 第10条:编写单元测试
    • 第二章:可读性
      • 第11条:为了可读性设计代码
      • 第12条:操作符的行为应该与其名称一致
      • 第13条:避免返回或操作 Unit?
      • 第14条: 在变量不清晰时指定其类型
      • 第15条:考虑显式引用接收者
      • 第16:属性应该代表状态,而非行为
      • 第17条:考虑使用具名参数
      • 第18条:遵守编程惯例
  • 第二部分:良好的设计
    • 第三章:可重用性
      • 第19条:不要重复知识
      • 第20条:不要重复实现常用算法
      • 第21条 使用属性代理来提取公共的属性模式
      • 第22条:当实现公共算法时使用泛型
      • 第23条:避免隐藏类型参数
      • 第24条:在使用泛型时考虑型变
      • 第25条:在不同的平台上提取公共模块进行重用
    • 第四章:抽象设计
      • 第26条:每个方法都应该基于单一的抽象级别而编写
      • 第27条:使用抽象来保护代码不受更改
      • 第28条:指定 Api 的稳定性
      • 第29条:考虑包装扩展 API
      • 第30条:最小化元素的可见性
      • 第31条:用文档定义合约
      • 第32条:遵守抽象合约
    • 第五章:对象的创建
      • 第33条:考虑使用工厂方法代替构造函数
      • 第34条:考虑带命名默认参数的主构造函数
      • 第35条:考虑为复杂的对象创建定义 DSL
    • 第六章:类的设计
      • 第36条:组合优于继承
      • 第37条:使用数据修饰符来表示一组数据
      • 第38条:使用函数类型而不是接口来传递操作和行为
      • 第39条:类层次结构优于标签类
      • 第40条:遵守 equals 的合约
      • 第41条:遵守 hashCode 的合约
      • 第42条:遵守 compareTo 的合约
      • 第43条: 考虑将 API 的非必要部分提取到扩展函数中
      • 第44条:避免在成员中定义扩展
  • 第三部分:性能
    • 第七章:让开发成本更低
      • 第45条:避免不必要的对象创建
      • 第46条:给高阶函数使用 inline 修饰符
      • 第47条:考虑使用内联类
      • 第48条:消除过时的对象引用
    • 第八章:高效的集合处理
      • 第49条:在具有多个处理步骤的大型集合上,优先使用 Sequence
      • 第50条:限制操作步骤的数量
      • 第51条:性能关键处考虑使用原语的数组
      • 第52条:考虑使用可变集合
Powered by GitBook
On this page
  1. 第二部分:良好的设计
  2. 第六章:类的设计

第38条:使用函数类型而不是接口来传递操作和行为

很多语言没有函数类型的概念,取而代之的是使用带有单一方法的接口,这种接口被称为 SAM(单一抽象方法)。下面是一个 SAM 用来传递当视图被点击时的回调信息的例子:

interface OnClick {
    fun clicked(view: View)
}

当一个函数接收一个 SAM 时,我们就必须传递一个实现了这个接口的对象实例:

fun setOnClickListener(listener: OnClick) {
    //...
}

setOnClickListener(object : OnClick {
    override fun clicked(view: View) {
        // ...
    }
})

然而,请注意用函数类型声明的参数会给我们更多的自由:

fun setOnClickListener(listener: (View) -> Unit) {
    //...
}

现在,我们可以传递这样的参数:

  • 一个 lambda 表达式或匿名函数

setOnClickListener { /*...*/ }
setOnClickListener(fun(view) { /*...*/ })
  • 一个函数引用或有界函数引用

setOnClickListener(::println)
setOnClickListener(this::showUsers)
  • 一个实现了这个被声明的函数类型的对象

class ClickListener: (View)->Unit {
    override fun invoke(view: View) {
        // ...
    }
}

setOnClickListener(ClickListener())

这些选项可以覆盖更广泛的用例范围。另一方面,有人可能会说 SAM 的优点在于它和它的参数都是命名的,请注意,我们也可以使用类型别名来命名函数类型:

typealias OnClick = (View) -> Unit

参数也可以被命名,命名它们的好处是:IDE 可以默认的建议展示出这些名称。

fun setOnClickListener(listener: OnClick) { /*...*/ }
typealias OnClick = (view: View)->Unit

注意,在使用 lambda 表达式时,也可以对参数进行解构,总之,这使得函数类型通常比 SAM 更好。

当我们要为监听器设置许多观察者时,使用函数类型是尤其正确的。典型的 Java 做法通常是在单个监听器接口中去收拢它们:

class CalendarView {
    var listener: Listener? = null
    
    interface Listener {
        fun onDateClicked(date: Date)
        fun onPageChanged(date: Date)
    }
}

我认为这很大程度是一种懒惰的做法,从 API 使用者的角度来看,最好将它们设置为包含函数类型的独立属性:

class CalendarView {
    var onDateClicked: ((date: Date) -> Unit)? = null
    var onPageChanged: ((date: Date) -> Unit)? = null
}

这样,onDataClicked 和 onPageChanged 实现就不需要在绑定在一个接口中,这些函数可以独立改变。

如果你没有定义接口的理由,那么最好使用函数类型,它们得到了很好的支持,并且经常被 Kotlin 开发人员使用。

我们什么时候需要 SAM?

当我们去设计一个从其它语言而不是 Kotlin 中使用的类时,这种情况下我们会更倾向使用 SAM。接口对于 Java 客户端来说更加干净,他们不能看到类型别名或名称建议。最后,在某些语言(特别是 Java)中使用 Kotlin 函数类型时,需要函数显式的返回 Unit:

// Kotlin
class CalendarView() {
    var onDateClicked: ((date: Date) -> Unit)? = null
    var onPageChanged: OnDateClicked? = null
}

interface OnDateClicked {
    fun onClick(date: Date)
}

// Java
CalendarView c = new CalendarView();
c.setOnDateClicked(date -> Unit.INSTANCE);
c.setOnPageChanged(date -> {});

这就是为什么当我们设计用于 Java 的 Api 时,使用 SAM 而不是函数类型是更合理的。但在其它情况下,首选函数类型。

Previous第37条:使用数据修饰符来表示一组数据Next第39条:类层次结构优于标签类

Last updated 2 years ago