Menu
Coddy logo textTech

Patrón Singleton

Parte de la sección Programación orientada a objetos del Journey de Kotlin de Coddy. Lección 42 de 57.

Un patrón de diseño es una solución comprobada a un problema de diseño que surge una y otra vez. El patrón singleton garantiza que una clase tenga exactamente una instancia a la que pueda acceder todo el programa. En Kotlin, una declaración object es un singleton: se crea una vez, en el primer uso, y de forma segura incluso cuando varios hilos se inician al mismo tiempo:

object Settings {
    var theme = "light"
    var fontSize = 14
}

fun openEditor() = "editor in ${Settings.theme} theme"

Dentro de main:

Settings.theme = "dark"
println(openEditor())
println(Settings.fontSize)

Salida:

editor in dark theme
14

El singleton contiene un estado que comparten distintas partes del programa. Mantén ese estado privado y cámbialo únicamente mediante métodos, para que cada cambio siga las mismas reglas:

object Metrics {
    private val hits = mutableMapOf<String, Int>()

    fun hit(page: String) { hits[page] = (hits[page] ?: 0) + 1 }
    fun report() = hits.entries.joinToString { "${it.key}=${it.value}" }
}

fun home() = Metrics.hit("home")
fun about() = Metrics.hit("about")

Dentro de main:

home(); about(); home()
println(Metrics.report())

Salida:

home=2, about=1

Cuando la instancia única necesita datos de configuración, una clase con un private constructor puede crearlos en su objeto complementario en la primera solicitud y devolver la misma instancia después:

class Database private constructor(val url: String) {
    companion object {
        private var instance: Database? = null

        fun connect(url: String): Database =
            instance ?: Database(url).also { instance = it }
    }
}

Dentro de main:

val a = Database.connect("db://main")
val b = Database.connect("db://other")
println(a === b)
println(b.url)

Salida:

true
db://main

Usa los singletons con moderación. Un singleton es un estado global: cualquier código puede cambiarlo, lo que oculta quién depende de él, y las pruebas no pueden comenzar con una copia nueva. Son adecuados para aquello que realmente existe una sola vez por programa, como la configuración o un registrador. Cuando solo una parte del programa necesita un objeto, pásalo como parámetro, tipado como una interfaz, para que una prueba pueda pasar uno falso en su lugar:

interface Clock { fun now(): Long }
object SystemClock : Clock { override fun now() = System.currentTimeMillis() }

class Session(private val clock: Clock) {      // la dependencia es visible
    val started = clock.now()
}
val session = Session(SystemClock)             // un test pasa un Clock falso
challenge icon

Desafío

Fácil

Completa el singleton Logger. Tiene un minLevel modificable (debug, info, warn o error, en ese orden). log(level, message) almacena [WARN] disk low cuando el nivel es como mínimo minLevel y, de lo contrario, cuenta el mensaje en dropped, que el código externo puede leer, pero no modificar. entries es una lista de solo lectura de las líneas almacenadas, y count(level) devuelve cuántas líneas almacenadas tienen ese nivel.

Las funciones auxiliares de Functions.kt se proporcionan: acceden al mismo Logger desde otro archivo. El código proporcionado lee el nivel mínimo y después las líneas level,message, e imprime cada entrada y errors: 1, dropped: 2.

Tu código va en Logger.kt y Functions.kt. Main.kt contiene el código proporcionado de entrada/salida y no se puede editar.

Pruébalo tú mismo

fun main() {
    // Código de entrada/salida suministrado: déjalo como está
    val input = generateSequence(::readLine).toList()
    Logger.minLevel = input[0]
    for (line in input.drop(1)) {
        val (level, message) = line.split(",", limit = 2)
        when (level) {
            "debug" -> debug(message)
            "info" -> info(message)
            "warn" -> warn(message)
            else -> error(message)
        }
    }
    for (e in Logger.entries) println(e)
    println("errors: ${Logger.count("error")}, dropped: ${Logger.dropped}")
}
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación orientada a objetos

Practica por tu cuenta: Playground de Kotlin