Constraining Visibility

If you leave a piece of code for a few days or weeks, then come back to it, you might see a much better way to write it.

This is one of the prime motivations for refactoring, which rewrites working code to make it more readable, understandable, and thus maintainable.

There is a tension in this desire to change and improve your code. Consumers (client programmers) require aspects of your code to be stable. You want to change it, and they want it to stay the same.

This is particularly important for libraries. Consumers of a library don’t want to rewrite code for a new version of that library. However, the library creator must be free to make modifications and improvements, with the certainty that the client code won’t be affected by those changes.

Therefore, a primary consideration in software design is:

Separate things that change from things that stay the same.

To control visibility, Kotlin and some other languages provide access modifiers. Library creators decide what is and is not accessible by the client programmer using the modifiers public, private, protected, and internal. This atom covers public and private, with a brief introduction to internal. We explain protected later in the book.

An access modifier such as private appears before the definition for a class, function, or property. An access modifier only controls access for that particular definition.

A public definition is accessible by client programmers, so changes to that definition impact client code directly. If you don’t provide a modifier, your definition is automatically public, so public is technically redundant. You will sometimes still specify public for the sake of clarity.

A private definition is hidden and only accessible from other members of the same class. Changing, or even removing, a private definition doesn’t directly impact client programmers.

private classes, top-level functions, and top-level properties are accessible only inside that file:

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

You can access private top-level properties ([1]), classes ([2]), and functions ([3]) from other functions and classes within RecordAnimals.kt. Kotlin prevents you from accessing a private top-level element from within another file, telling you it’s private in the file:

// 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!
*/

Privacy is most commonly used for members of a class:

// 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] A private property, not accessible outside the containing class.
  • [2] A private member function.
  • [3] A public member function, accessible to anyone.
  • [4] No access modifier means public.
  • [5] Only members of the same class can access private members.

The private keyword means no one can access that member except other members of that class. Other classes cannot access private members, so it’s as if you’re also insulating the class against yourself and your collaborators. With private, you can freely change that member without worrying whether it affects another class in the same package. As a library designer you’ll typically keep things as private as possible, and expose only functions and classes to client programmers.

Any member function that is a helper function for a class can be made private to ensure you don’t accidentally use it elsewhere in the package and thus prohibit yourself from changing or removing that function.

The same is true for a private property inside a class. Unless you must expose the underlying implementation (which is less likely than you might think), make properties private. However, just because a reference to an object is private inside a class doesn’t mean some other object can’t have a public reference to the same object:

// 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 is now defined in the scope surrounding the creation of the CounterHolder object on the following line.
  • [2] Passing c as the argument to the CounterHolder constructor means that the new CounterHolder now refers to the same Counter object that c refers to.
  • [3] The Counter that is supposedly private inside ch can still be manipulated via c.
  • [4] Counter(9) has no other references except within CounterHolder, so it cannot be accessed or modified by anything except ch2.

Maintaining multiple references to a single object is called aliasing and can produce surprising behavior.

Modules

Unlike the small examples in this book, real programs are often large. It can be helpful to divide such programs into one or more modules. A module is a logically independent part of a codebase. The way you divide a project into modules depends on the build system (such as Gradle or Maven) and is beyond the scope of this book.

An internal definition is accessible only inside the module where it is defined. internal lands somewhere between private and public—use it when private is too restrictive but you don’t want an element to be a part of the public API. We do not use internal in the book’s examples or exercises.

Modules are a higher-level concept. The following atom introduces packages, which enable finer-grained structuring. A library is often a single module consisting of multiple packages, so internal elements are available within the library but are not accessible by consumers of that library.

Exercises and solutions can be found at www.AtomicKotlin.com.