Singleton-Muster
Teil des Abschnitts Objektorientierte Programmierung der Kotlin-Journey von Coddy. Lektion 42 von 57.
Ein Entwurfsmuster ist eine bewährte Lösung für ein Entwurfsproblem, das immer wieder auftritt. Das Singleton-Muster stellt sicher, dass eine Klasse genau eine Instanz besitzt, die das gesamte Programm erreichen kann. In Kotlin ist eine object-Deklaration ein Singleton: Sie wird bei der ersten Verwendung einmal erstellt und ist auch dann sicher, wenn mehrere Threads gleichzeitig starten:
object Settings {
var theme = "light"
var fontSize = 14
}
fun openEditor() = "editor in ${Settings.theme} theme"Innerhalb von main:
Settings.theme = "dark"
println(openEditor())
println(Settings.fontSize)Ausgabe:
editor in dark theme
14Das Singleton enthält einen Zustand, den verschiedene Teile des Programms gemeinsam nutzen. Halte diesen Zustand privat und ändere ihn nur über Methoden, damit jede Änderung denselben Regeln folgt:
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")Innerhalb von main:
home(); about(); home()
println(Metrics.report())Ausgabe:
home=2, about=1Wenn die einzelne Instanz Einrichtungsdaten benötigt, kann eine Klasse mit einem private constructor sie bei der ersten Anfrage in ihrem Companion-Objekt erstellen und danach dieselbe Instanz zurückgeben:
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 }
}
}Innerhalb von main:
val a = Database.connect("db://main")
val b = Database.connect("db://other")
println(a === b)
println(b.url)Ausgabe:
true
db://mainVerwende Singletons sparsam. Ein Singleton ist ein globaler Zustand: Jeder Code kann ihn ändern, wodurch verborgen bleibt, wer von ihm abhängt, und Tests können nicht mit einer frischen Kopie beginnen. Sie eignen sich für Dinge, die tatsächlich nur einmal pro Programm vorkommen, etwa die Konfiguration oder einen Logger. Wenn nur ein Teil des Programms ein Objekt benötigt, übergib es als Parameter, typisiert als Interface, damit ein Test stattdessen ein Fake übergeben kann:
interface Clock { fun now(): Long }
object SystemClock : Clock { override fun now() = System.currentTimeMillis() }
class Session(private val clock: Clock) { // die Abhängigkeit ist sichtbar
val started = clock.now()
}
val session = Session(SystemClock) // ein Test übergibt eine gefälschte ClockAufgabe
EinfachVervollständige das Singleton Logger. Es verfügt über eine änderbare minLevel (debug, info, warn oder error, in dieser Reihenfolge). log(level, message) speichert [WARN] disk low, wenn die Ebene mindestens minLevel entspricht, und zählt die Nachricht andernfalls in dropped, das außerhalb des Codes gelesen, aber nicht geändert werden kann. entries ist eine schreibgeschützte Liste der gespeicherten Zeilen, und count(level) gibt zurück, wie viele gespeicherte Zeilen diese Ebene haben.
Die Hilfsfunktionen in Functions.kt werden bereitgestellt: Sie greifen von einer anderen Datei aus auf dasselbe Logger zu. Der bereitgestellte Code liest die minimale Ebene und anschließend Zeilen im Format level,message ein und gibt jeden Eintrag sowie errors: 1, dropped: 2 aus.
Dein Code gehört in Logger.kt und Functions.kt. Main.kt enthält den bereitgestellten Eingabe-/Ausgabecode und kann nicht bearbeitet werden.
Probier es selbst
fun main() {
// Gelieferter Ein-/Ausgabecode: belasse ihn wie er ist
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}")
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Klassen und Objekte
Arbeiten mit DateienKlassen definierenPrimärkonstruktoreninit-Blöcke und ValidierungSekundärkonstruktorenRückblick – Bibliotheksausweis4Vererbung
Offene Klassen und UnterklassenÜberschreiben von Membernsuper und KonstruktorenAbstrakte KlassenRückblick – Mitarbeiterverzeichnis2Eigenschaften und Kapselung
Benutzerdefinierte ZugriffsmethodenSichtbarkeitsmodifikatorenPrivate Setterlazy und lateinitWiederholung – Bankkonto5Schnittstellen
Schnittstellen deklarierenStandardmethodenMehrere SchnittstellenSchnittstellen als TypenRückblick – Zahlungsmethoden8Generics
Generische FunktionenGenerische KlassenTypbeschränkungenVarianz mit in und outRückblick – Prioritätswarteschlange11Projekt: Bibliotheksverwaltung
Bücher und MitgliederBücher ausleihenÜbe selbstständig: Kotlin-Playground