Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

限制可见性

如果您将一段代码放置了几天或几周,然后再回来看,您可能会发现更好的编写方式。

这是重构的主要动机之一,重构会重写现有的代码,使其更易读、更易理解,从而更易于维护。

在这种改变和改进代码的欲望中存在一种紧张关系。消费者(客户程序员)需要代码的某些方面保持稳定。您想要进行更改,而他们希望保持不变。

这在库中尤其重要。库的使用者不希望为库的新版本重写代码。但是,库的创建者必须有自由进行修改和改进的权利,并确保客户端代码不会受到这些更改的影响。

因此,软件设计中的一个主要考虑因素是:

将可能发生更改的内容与保持不变的内容分开。

为了控制可见性,Kotlin 和其他一些语言提供了访问修饰符。库的创建者使用修饰符 publicprivateprotectedinternal 来决定客户程序员可以访问什么内容和不可以访问什么内容。本部分涵盖了 publicprivate,并简要介绍了 internal。我们将在本书的后续部分解释 protected

诸如 private 之类的访问修饰符出现在类、函数或属性的定义之前。访问修饰符仅控制对该特定定义的访问。

public 定义可以被客户程序员访问,因此对该定义的更改会直接影响客户端代码。如果您不提供修饰符,您的定义将自动为 public,因此 public 在技术上是多余的。出于清晰起见,有时您仍然会指定 public

private 定义是隐藏的,只能从同一个类的其他成员访问。更改或甚至删除 private 定义不会直接影响客户程序员。

private 类、顶级函数和顶级属性只能在同一个文件内访问:

// Visibility/RecordAnimals.kt

private var index = 0                  // [1]

private class Animal(val name: String) // [2]

private fun recordAnimal(              // [3]
  animal: Animal
) {
  println("Animal #$index: ${animal.name}")
  index++
}

fun recordAnimals() {
  recordAnimal(Animal("Tiger"))
  recordAnimal(Animal("Antelope"))
}

fun recordAnimalsCount() {
  println("$index animals are here!")
}

您可以从 RecordAnimals.kt 的其他函数和类中访问 private 的顶级属性([1])、类([2])和函数([3])。Kotlin 阻止您从另一个文件中访问 private 的顶级元素,告诉您它在文件中是 private 的:

// Visibility/ObserveAnimals.kt

fun main() {
  // 无法访问在其他文件中声明的 private 成员。
  // 类是私有的:
  // val rabbit = Animal("Rabbit")
  // 函数是私有的:
  // recordAnimal(rabbit)
  // 属性是私有的:
  // index++

  recordAnimals()
  recordAnimalsCount()
}
/* 输出:
Animal #0: Tiger
Animal #1: Antelope
2 animals are here!
*/

隐私性在类的成员中最常用:

// Visibility/Cookie.kt

class Cookie(
  private var isReady: Boolean  // [1]
) {
  private fun crumble() =       // [2]
    println("crumble")

  public fun bite() =           // [3]
    println("bite")

  fun eat() {                   // [4]
    isReady = true              // [5]
    crumble()
    bite()
  }
}

fun main() {
  val x = Cookie(false)
  x.bite()
  // 无法访问私有成员:
  // x.isReady
  // x.crumble()
  x.eat()
}
/* 输出:
bite
crumble
bite
*/
  • [1] private 属性,在包含的类外部无法访问。
  • [2] private 成员函数。
  • [3] public 成员函数,可供任何人访问。
  • [4] 没有访问修饰符表示为 public
  • [5] 只有相同类的成员可以访问 private 成员。

private 关键字意味着除了同一类的其他成员外,没有人可以访问该成员。其他类无法访问 private 成员,因此就好像您还将该类与自己和协作者隔离开来。使用 private,您可以自由更改该成员,而不必担心它是否会影响同一包中的另一个类。作为库设计者,您通常会尽可能将事物设置为 private,仅向客户程序员公开函数和类。

对于类的任何辅助函数(helper function)来说,如果要确保不会在包的其他地方意外使用它,可以将其设置为 private,从而禁止自己在更改或删除该函数时使用它。

在类内部存在的 private 属性也是如此。除非必须公开底层实现(这比您可能认为的要少),否则将属性设置为 private。但是,仅因为类内部的引用是 private,并不意味着其他对象不能对同一对象拥有 public 引用:

// Visibility/MultipleRef.kt

class Counter(var start: Int) {
  fun increment() {
    start += 1
  }
  override fun toString() = start.toString()
}

class CounterHolder(counter: Counter) {
  private val ctr = counter
  override fun toString() =
    "CounterHolder: " + ctr
}

fun main() {
  val c = Counter(11)                 // [1]
  val ch = CounterHolder(c)           // [2]
  println(ch)
  c.increment()                       // [3]
  println(ch)
  val ch2 = CounterHolder(Counter(9)) // [4]
  println(ch2)
}
/* 输出:
CounterHolder: 11
CounterHolder: 12
CounterHolder: 9
*/
  • [1] c 现在在创建 CounterHolder 对象的范围内。
  • [2]c 作为参数传递给 CounterHolder 构造函数,意味着新的 CounterHolder 现在引用与 c 相同的 Counter 对象。
  • [3]ch 内部被假定为 privateCounter 仍然可以通过 c 进行操作。
  • [4] Counter(9) 除了在 CounterHolder 内部没有其他引用,因此除了 ch2 之外,没有任何东西可以访问或修改它。

维护单个对象的多个引用称为别名,可能会产生令人惊讶的行为。

模块

与本书中的小示例不同,实际程序通常很大。将这些程序分成一个或多个模块可能会有所帮助。模块是代码库的逻辑独立部分。将项目分成模块的方式取决于构建系统(例如 GradleMaven),这超出了本书的范围。

internal 定义仅在定义它的模块内部可访问。internal 处于 privatepublic 之间 - 当 private 过于严格但不想要元素成为 public API 的一部分时,使用它是合适的。本书的示例和练习中未使用 internal

模块是一个更高级的概念。下一节将介绍,这可以实现更精细的结构。库通常是一个由多个包组成的单一模块,因此 internal 元素在库内部是可用的,但对该库的使用者是不可访问的。

练习和解答可以在 www.AtomicKotlin.com 找到。