Pattern Singleton
Fa parte della sezione Programmazione orientata agli oggetti del percorso Kotlin di Coddy. Lezione 42 di 57.
Un modello di progettazione è una soluzione collaudata a un problema di progettazione che si ripresenta più e più volte. Il modello singleton garantisce che una classe abbia esattamente un’istanza a cui può accedere l’intero programma. In Kotlin, una dichiarazione object è un singleton: viene creato una sola volta, al primo utilizzo, e in modo sicuro anche quando più thread si avviano contemporaneamente:
object Settings {
var theme = "light"
var fontSize = 14
}
fun openEditor() = "editor in ${Settings.theme} theme"All’interno di main:
Settings.theme = "dark"
println(openEditor())
println(Settings.fontSize)Output:
editor in dark theme
14Il singleton contiene uno stato condiviso da diverse parti del programma. Mantieni privato quello stato e modificalo solo tramite metodi, così ogni modifica segue le stesse regole:
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")All'interno di main:
home(); about(); home()
println(Metrics.report())Output:
home=2, about=1Quando l’istanza singola ha bisogno di dati di configurazione, una classe con un private constructor può crearla nel proprio oggetto companion alla prima richiesta e restituire la stessa istanza in seguito:
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 }
}
}All’interno di main:
val a = Database.connect("db://main")
val b = Database.connect("db://other")
println(a === b)
println(b.url)Output:
true
db://mainUsa i singleton con parsimonia. Un singleton è uno stato globale: qualsiasi codice può modificarlo, il che nasconde chi ne dipende, e i test non possono partire da una copia nuova. Sono adatti a ciò che è davvero unico per programma, come la configurazione o un logger. Quando solo una parte del programma ha bisogno di un oggetto, passaglielo come parametro, tipizzato come un'interfaccia, così un test può passare un falso al suo posto:
interface Clock { fun now(): Long }
object SystemClock : Clock { override fun now() = System.currentTimeMillis() }
class Session(private val clock: Clock) { // la dipendenza è visibile
val started = clock.now()
}
val session = Session(SystemClock) // un test passa un Clock fittizioSfida
FacileCompleta il singleton Logger. Ha un minLevel modificabile (debug, info, warn o error, in quest’ordine). log(level, message) memorizza [WARN] disk low quando il livello è almeno pari a minLevel e altrimenti conteggia il messaggio in dropped, che il codice esterno può leggere ma non modificare. entries è un elenco di sola lettura delle righe memorizzate, e count(level) restituisce il numero di righe memorizzate con quel livello.
Le funzioni helper in Functions.kt sono fornite: raggiungono lo stesso Logger da un altro file. Il codice fornito legge il livello minimo e poi le righe level,message, quindi stampa ogni voce e errors: 1, dropped: 2.
Il tuo codice va in Logger.kt e Functions.kt. Main.kt contiene il codice fornito per l’input/output e non può essere modificato.
Provalo tu
fun main() {
// Codice di input/output fornito: lascialo così com'è
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}")
}
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Classi e oggetti
Lavorare con i fileDefinire le classiCostruttori primariBlocchi init e convalidaCostruttori secondariRiepilogo - Tessera della biblioteca4Ereditarietà
Classi aperte e sottoclassiRidefinizione dei membrisuper e costruttoriClassi astratteRiepilogo - Elenco del personale2Proprietà e incapsulamento
Accessor personalizzatiModificatori di visibilitàSetter privatilazy e lateinitRipasso - Conto bancario5Interfacce
Dichiarare le interfacceMetodi predefinitiInterfacce multipleLe interfacce come tipiRiepilogo - Metodi di pagamento8Tipi generici
Funzioni genericheClassi genericheVincoli sui tipiVarianza con in e outRiepilogo - Coda con priorità11Progetto: Gestione della biblioteca
Libri e sociPrendere in prestito i libriEsercitati da solo: Playground Kotlin