Everything else is just a tool to model your domain in a way that’s easier to read and reason about, and to produce much shorter code. Interfaces define module boundaries and can be mocked to enable simpler testing. Through classes, objects, and data structures, you can model your domain entities. Enums make a list of limited options crystal clear. Extensions allow you to add functionality to existing types, without having access to their original source code, giving advantage of better logic structure. With generics, you can write universal code, promoting reuse. And overloaded operators give that extra edge to common operations that benefit from succinct, crisp syntax.

Extensions

It is possible to extend functionality of existing types without resorting to inheritance. It is also possible to program standard, reusable functionality using delegation.

Property wrappers

To intercept a property and make it behave in a special way that’s easy to reuse, for example, storing its value in a key-value map or tracking its changes, you can use property delegates. Such properties are called delegated properties.

Property delegates don’t have to implement any formal interfaces; they just need to provide special methods.

Read-only

Read-only delegates should provide a getValue(...) method.

import kotlin.reflect.KProperty

class TreasuryDelegate(
  private val treasure: String,
  private var quantity: Int
) {
  operator fun getValue(thisRef: Any?, property: KProperty<*>): String? {
    if (quantity == 0) return null
    quantity -= 1
    return treasure
  }
}

val limitedToken: String? by TreasuryDelegate("precious", quantity = 5)

To avoid defining a new type for each delegate and polluting the name space, ReadOnlyProperty interface is provided as a convenience, so the delegate can be implemented via a function.

import kotlin.reflect.KProperty
import kotlin.properties.ReadOnlyProperty

fun treasury(treasure: String, quantity: Int): ReadOnlyProperty<Any?, String?>
  = object: ReadOnlyProperty<Any?, String?> {
    private var count = quantity

    override fun getValue(thisRef: Any?, property: KProperty<*>): String? {
      if (count == 0) return null
      count -= 1
      return treasure
    }
  }

val alsoLimitedToken: String? by treasury("precious", quantity = 5)

Read-write

Read-write delegates should additionally provide a setValue(...) method.

import kotlin.reflect.KProperty

enum class TextTransform {
    UPPERCASE, LOWERCASE
}

class TextTransformDelegate(
  private var text: String, 
  private val transform: TextTransform
) {
  operator fun getValue(thisRef: Any?, property: KProperty<*>): String
    = "${property.name}: " + when (transform) {
      TextTransform.UPPERCASE -> text.uppercase()
      TextTransform.LOWERCASE -> text.lowercase()
    }

  operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
    text = value
  }
}

var formattedText: String by TextTransformDelegate(
  "whatever", 
  transform = TextTransform.UPPERCASE
)
formattedText = "hello"

ReadWriteProperty convenience interface can be used to avoid new types and implement the delegate via a function.

import kotlin.reflect.KProperty
import kotlin.properties.ReadWriteProperty

fun textTransform(
  initialText: String, 
  transform: TextTransform
): ReadWriteProperty<Any?, String>
  = object: ReadWriteProperty<Any?, String> {
    private var text = initialText

    override fun getValue(thisRef: Any?, property: KProperty<*>): String
      = "${property.name}: " + when (transform) {
        TextTransform.UPPERCASE -> text.uppercase()
        TextTransform.LOWERCASE -> text.lowercase()
      }

    override fun setValue(
      thisRef: Any?, 
      property: KProperty<*>, 
      value: String
    ) {
      text = value
    }
  }

var alsoFormattedText: String by textTransform(
  "whatever", 
  transform = TextTransform.UPPERCASE
)
alsoFormattedText = "hello"

Delegated properties can also delegate to global variables, visible properties of other objects, or other properties of the same object. Local variables, inside a function, can use delegation, too.

The example moreover shows how to use the pattern for evolving API, deprecating old properties and proxying to them until a rewrite is ready.

@Deprecated(
  "Global 'limitedToken' is deprecated", 
  ReplaceWith("DelegationContext.limitedToken")
)
val limitedToken: String? by TreasuryDelegate("precious", quantity = 5)

data class Storage(val data: String)

class DelegationContext(storage: Storage) {
  val treasure: String? by TreasuryDelegate("precious", quantity = 5)
  var formattedText: String by textTransform(
    "whatever", transform = TextTransform.UPPERCASE
  )

  @Deprecated("Also deprecated", ReplaceWith("veryLimitedToken"))
  val limitedToken: String? by ::limitedToken
  val veryLimitedToken: String? by this::limitedToken

  val data: String by storage::data

  fun localContext() {
    val treasure: String? by TreasuryDelegate("precious", quantity = 5)
    var formattedText: String by textTransform(
      "whatever", transform = TextTransform.UPPERCASE
    )

    this.formattedText = "hello"
    formattedText = "hello"
  }
}

Delegate provider

It is possible to insert one more level of indirection when delegating, for example, to validate the property to which the delegate is applied or even provide a different delegate implementation dynamically. To that end, a provideDelegate(...) operator method needs to be implemented. It can return any property delegate, like ReadOnlyProperty, ReadWriteProperty, or a custom class that just contains getValue(...) or setValue(...) methods.

PropertyDelegateProvider convenience interface can be used to avoid new types and implement the delegate provider via a function.

import kotlin.reflect.KProperty
import kotlin.properties.ReadOnlyProperty
import kotlin.properties.PropertyDelegateProvider

class GuardianAtTheGate(
  private val treasure: String,
  private var quantity: Int
) {
  operator fun provideDelegate(
    thisRef: Any?, 
    property: KProperty<*>
  ): ReadOnlyProperty<Any?, String?> {
    if (!property.name.lowercase().endsWith("treasure")) 
      throw Exception("Wrong name!")
    return treasury(treasure, quantity)
  }
}

fun guardian(
  treasure: String, 
  quantity: Int
): PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, String?>>
  = PropertyDelegateProvider { thisRef: Any?, property ->
    if (!property.name.lowercase().endsWith("treasure")) 
      throw Exception("Wrong name!")
    treasury(treasure, quantity)
  }

val sacredTreasure: String? by GuardianAtTheGate("one of a kind", quantity = 1)
val alsoSacredTreasure: String? by guardian("one of a kind", quantity = 1)

[--]