Einschränkung der Sichtbarkeit

Wenn Sie ein Stück Code für ein paar Tage oder Wochen liegen lassen und dann zurückkehren, sehen Sie möglicherweise eine viel bessere Möglichkeit, es zu schreiben.

Dies ist eine der Hauptmotivationen für das Refactoring, das funktionierenden Code umschreibt, um ihn lesbarer, verständlicher und damit wartbarer zu machen.

Es gibt eine Spannung in diesem Wunsch, Ihren Code zu ändern und zu verbessern. Verbraucher (Anwenderprogrammierer) erfordern, dass Aspekte Ihres Codes stabil bleiben. Sie möchten es ändern, und sie wollen, dass es gleich bleibt.

Dies ist besonders wichtig für Bibliotheken. Verbraucher einer Bibliothek wollen nicht den Code für eine neue Version dieser Bibliothek umschreiben. Der Bibliotheksentwickler muss jedoch frei sein, Änderungen und Verbesserungen vorzunehmen, mit der Gewissheit, dass der Client-Code von diesen Änderungen nicht betroffen sein wird.

Daher ist eine primäre Überlegung im Softwaredesign:

Trenne Dinge, die sich ändern, von Dingen, die gleich bleiben.

Um die Sichtbarkeit zu steuern, bieten Kotlin und einige andere Sprachen Zugriffsmodifikatoren. Bibliotheksentwickler entscheiden mit den Modifikatoren public, private, protected und internal, was für den Anwenderprogrammierer zugänglich ist und was nicht. Dieses Kapitel behandelt public und private, mit einer kurzen Einführung in internal. Wir erklären protected später im Buch.

Ein Zugriffsmodifikator wie private erscheint vor der Definition einer Klasse, Funktion oder Eigenschaft. Ein Zugriffsmodifikator steuert nur den Zugriff für diese spezielle Definition.

Eine public Definition ist für Anwenderprogrammierer zugänglich, sodass Änderungen an dieser Definition den Client-Code direkt beeinflussen. Wenn Sie keinen Modifikator angeben, ist Ihre Definition automatisch public, daher ist public technisch gesehen redundant. Manchmal geben Sie dennoch public zur Klarstellung an.

Eine private Definition ist verborgen und nur von anderen Mitgliedern derselben Klasse zugänglich. Änderungen oder sogar das Entfernen einer private Definition beeinflussen die Anwenderprogrammierer nicht direkt.

private Klassen, Top-Level-Funktionen und Top-Level-Eigenschaften sind nur innerhalb dieser Datei zugänglich:

// 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!")
}

Sie können auf private Top-Level-Eigenschaften ([1]), Klassen ([2]) und Funktionen ([3]) von anderen Funktionen und Klassen innerhalb von RecordAnimals.kt zugreifen. Kotlin verhindert, dass Sie auf ein private Top-Level-Element aus einer anderen Datei zugreifen, indem es Ihnen mitteilt, dass es in der Datei private ist:

// Visibility/ObserveAnimals.kt

fun main() {
  // Can't access private members
  // declared in another file.
  // Class is private:
  // val rabbit = Animal("Rabbit")
  // Function is private:
  // recordAnimal(rabbit)
  // Property is private:
  // index++

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

Sichtbarkeit wird am häufigsten für Mitglieder einer Klasse verwendet:

// 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()
  // Can't access private members:
  // x.isReady
  // x.crumble()
  x.eat()
}
/* Output:
bite
crumble
bite
*/
  • [1] Eine private Eigenschaft, die außerhalb der umgebenden Klasse nicht zugänglich ist.
  • [2] Eine private Mitgliedsfunktion.
  • [3] Eine public Mitgliedsfunktion, die für jeden zugänglich ist.
  • [4] Kein Zugriffsmodifikator bedeutet public.
  • [5] Nur Mitglieder derselben Klasse können auf private Mitglieder zugreifen.

Das Schlüsselwort private bedeutet, dass niemand auf dieses Mitglied zugreifen kann, außer anderen Mitgliedern dieser Klasse. Andere Klassen können nicht auf private Mitglieder zugreifen, sodass es so ist, als würden Sie die Klasse auch gegen sich selbst und Ihre Mitarbeiter abschirmen. Mit private können Sie dieses Mitglied nach Belieben ändern, ohne sich Sorgen machen zu müssen, ob es eine andere Klasse im selben Paket betrifft. Als Bibliotheksentwickler werden Sie typischerweise so viel wie möglich als private halten und nur Funktionen und Klassen für die Benutzerprogrammierer freigeben.

Jede Mitgliedsfunktion, die eine Hilfsfunktion für eine Klasse ist, kann private gemacht werden, um sicherzustellen, dass Sie sie nicht versehentlich anderswo im Paket verwenden und sich dadurch daran hindern, diese Funktion zu ändern oder zu entfernen.

Dasselbe gilt für eine private Eigenschaft innerhalb einer Klasse. Es sei denn, Sie müssen die zugrunde liegende Implementierung offenlegen (was weniger wahrscheinlich ist, als Sie vielleicht denken), machen Sie Eigenschaften private. Allerdings bedeutet eine private Referenz auf ein Objekt innerhalb einer Klasse nicht, dass ein anderes Objekt nicht eine public Referenz auf dasselbe Objekt haben kann:

// 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)
}
/* Output:
CounterHolder: 11
CounterHolder: 12
CounterHolder: 9
*/
  • [1] c ist jetzt im Geltungsbereich definiert, der die Erstellung des CounterHolder-Objekts in der folgenden Zeile umgibt.
  • [2] c als Argument an den CounterHolder-Konstruktor zu übergeben, bedeutet, dass der neue CounterHolder nun auf dasselbe Counter-Objekt verweist, auf das auch c verweist.
  • [3] Der Counter, der angeblich privat innerhalb von ch ist, kann dennoch über c manipuliert werden.
  • [4] Counter(9) hat keine anderen Referenzen außer innerhalb von CounterHolder, daher kann es nicht von etwas anderem als ch2 zugegriffen oder modifiziert werden.

Mehrere Referenzen auf ein einzelnes Objekt zu haben, wird als Aliasing bezeichnet und kann überraschendes Verhalten hervorrufen.

Module

Im Gegensatz zu den kleinen Beispielen in diesem Buch sind reale Programme oft groß. Es kann hilfreich sein, solche Programme in ein oder mehrere Module zu unterteilen. Ein Modul ist ein logisch unabhängiger Teil eines Codebestands. Die Art und Weise, wie Sie ein Projekt in Module unterteilen, hängt vom Build-System ab (wie Gradle oder Maven) und liegt außerhalb des Rahmens dieses Buches.

Eine interne Definition ist nur innerhalb des Moduls zugänglich, in dem sie definiert ist. Intern liegt irgendwo zwischen privat und öffentlich—verwenden Sie es, wenn privat zu restriktiv ist, Sie aber nicht möchten, dass ein Element Teil der öffentlichen API ist. Wir verwenden intern nicht in den Beispielen oder Übungen des Buches.

Module sind ein höheres Konzept. Der folgende Abschnitt führt Pakete ein, die eine feinere Strukturierung ermöglichen. Eine Bibliothek ist oft ein einziges Modul, das aus mehreren Paketen besteht, sodass interne Elemente innerhalb der Bibliothek verfügbar sind, jedoch nicht von den Verbrauchern dieser Bibliothek zugänglich sind.

Übungen und Lösungen finden Sie unter www.AtomicKotlin.com.