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
14El 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=1Cuando 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://mainUsa 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 falsoDesafío
FácilCompleta 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}")
}
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
1Clases y objetos
Trabajar con archivosDefinir clasesConstructores primariosBloques init y validaciónConstructores secundariosRepaso - Tarjeta de biblioteca4Herencia
Clases abiertas y subclasesSobrescritura de miembrossuper y constructoresClases abstractasRepaso: directorio del personal2Propiedades y encapsulación
Accesores personalizadosModificadores de visibilidadSetters privadoslazy y lateinitRepaso: cuenta bancaria5Interfaces
Declaración de interfacesMétodos predeterminadosInterfaces múltiplesInterfaces como tiposRepaso: métodos de pago8Genéricos
Funciones genéricasClases genéricasRestricciones de tiposVariancia con in y outRepaso - Cola de prioridad11Proyecto: Gestión de biblioteca
Libros y miembrosPréstamo de librosPractica por tu cuenta: Playground de Kotlin