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. 第六章:类的设计

第44条:避免在成员中定义扩展

当我们为某个类定义扩展函数时,它不会作为成员添加到这个类中。扩展函数是一种不同的函数,我们调用第一个参数,叫做接收者。在底层,扩展函数被编译为普通函数,接收者作为第一个参数,例如下面的函数:

fun String.isPhoneNumber(): Boolean =
    length == 7 && all { it.isDigit() }

在底层,它被编译为类似下面这段的函数:

fun isPhoneNumber(`$this`: String): Boolean =
    `$this`.length == 7 && `$this`.all { it.isDigit() }

了解这些后,我们可以定义成员扩展函数,甚至在接口中定义它:

interface PhoneBook {
    fun String.isPhoneNumber(): Boolean
}

class Fizz: PhoneBook {
    override fun String.isPhoneNumber(): Boolean =
        length == 7 && all { it.isDigit() }
}

即使这是可以做到的,但我们有很好的的理由去避免定义成员扩展函数/属性(DSL除外)。特别是,不要仅仅为了限制可见性而将扩展定义为成员。

// 不好的做法,千万不要这样做
class PhoneBookIncorrect {
    // ...
    
    fun String.isPhoneNumber() =
        length == 7 && all { it.isDigit() }
}

一个很大的原因是它并没有真正限制可见性,这只会让扩展函数变得更加复杂,因为用户需要同时提供扩展和分发接收者:

PhoneBookIncorrect().apply { "1234567890".test() }

你应该使用可见性修饰符来限制扩展的可见性,而不是将其作为成员。

// 这是我们如何给扩展函数限制可见性的
class PhoneBookCorrect {
    // ...
}

private fun String.isPhoneNumber() =
    length == 7 && all { it.isDigit() }

我们倾向避免扩展成员有几个很好的理由:

  • 这样做不支持引用:

val ref = String::isPhoneNumber
val str = "1234567890"
val boundedRef = str::isPhoneNumber

val refX = PhoneBookIncorrect::isPhoneNumber // ERROR
val book = PhoneBookIncorrect()
val boundedRefX = book::isPhoneNumber // ERROR
  • 对有两个接收者的隐式访问可能会令人困惑:

class A {
    val a = 10
}
class B {
    val a = 20
    val b = 30
    
    fun A.test() = a + b // 它是40还是50?
}
  • 当我们期望一个扩展去修改或引用一个接收者时,我们不清楚这个扩展函数作用在哪个接收者上:

class A {
    //...
}
class B {
    //...
    fun A.update() = ... // 这个 update 函数是针对A的,还是针对B的???
}
  • 对于经验较少的开发者来说,看到成员扩展可能会违反直觉或让人感到害怕

总而言之,如果有很好的理由去使用成员扩展,这还好。只是要意识到它所到来的缺点,能避免就尽量避免。要限制可见性,请使用可见性修饰符。仅仅在类中放置一个扩展并不会限制它在外部的使用。

Previous第43条: 考虑将 API 的非必要部分提取到扩展函数中Next第七章:让开发成本更低

Last updated 2 years ago