Beobachtermuster
Teil des Abschnitts Objektorientierte Programmierung der Kotlin-Journey von Coddy. Lektion 44 von 57.
Das Beobachter-Muster ermöglicht es Objekten, die Ereignisse eines anderen Objekts, des Subjekts, zu abonnieren. Das Subjekt führt eine Liste von Beobachtern und benachrichtigt jeden einzelnen, wenn etwas passiert, ohne zu wissen, was sie damit tun:
interface PriceListener {
fun priceChanged(symbol: String, price: Int)
}
class Stock(val symbol: String) {
private val listeners = mutableListOf<PriceListener>()
var price = 0
set(value) {
field = value
for (l in listeners) l.priceChanged(symbol, value)
}
fun subscribe(l: PriceListener) { listeners.add(l) }
}
class Ticker : PriceListener {
override fun priceChanged(symbol: String, price: Int) = println("$symbol is now $price")
}Innerhalb von main:
val acme = Stock("ACME")
acme.subscribe(Ticker())
acme.price = 120
acme.price = 95Ausgabe:
ACME is now 120
ACME is now 95In Kotlin ist ein Beobachter oft eine einfache Funktion. Das Subjekt speichert Lambdas und ruft jedes einzelne auf, daher benötigt ein neuer Beobachter keine Klasse:
class Button(val label: String) {
private val handlers = mutableListOf<(String) -> Unit>()
fun onClick(handler: (String) -> Unit) { handlers.add(handler) }
fun click() { for (h in handlers) h(label) }
}Innerhalb von main:
var clicks = 0
val save = Button("Save")
save.onClick { println("$it clicked") }
save.onClick { clicks++ }
save.click()
save.click()
println("clicks: $clicks")Ausgabe:
Save clicked
Save clicked
clicks: 2Ein Beobachter möchte möglicherweise aufhören zuzuhören. Wenn subscribe eine Funktion zurückgibt, die den Beobachter wieder entfernt, kann der Aufrufer abbrechen, ohne Zugriff auf die Liste des Subjekts zu benötigen:
class Feed {
private val readers = mutableListOf<(String) -> Unit>()
fun subscribe(reader: (String) -> Unit): () -> Unit {
readers.add(reader)
return { readers.remove(reader) }
}
fun post(text: String) { for (r in readers.toList()) r(text) }
}Innerhalb von main:
val feed = Feed()
val cancel = feed.subscribe { println("ada reads: $it") }
feed.subscribe { println("bo reads: $it") }
feed.post("hello")
cancel()
feed.post("again")Ausgabe:
ada reads: hello
bo reads: hello
bo reads: againDie Standardbibliothek verfügt über eine beobachtbare Eigenschaft. Delegates.observable(initial) { property, old, new -> ... } führt das Lambda nach jeder Zuweisung aus; Kapitel 10 erklärt das dabei verwendete Schlüsselwort by:
import kotlin.properties.Delegates
class Thermostat {
var target: Int by Delegates.observable(20) { _, old, new ->
println("target changed from $old to $new")
}
}Innerhalb von main:
val t = Thermostat()
t.target = 22
t.target = 18Ausgabe:
target changed from 20 to 22
target changed from 22 to 18Aufgabe
EinfachEin WeatherStation benachrichtigt Beobachter, die TemperatureObserver implementieren. Schreibe attach(observer), detach(observer) und record(temp), wobei jeder angehängte Beobachter benachrichtigt wird, jedoch nur, wenn sich die Temperatur von der vorherigen Messung unterscheidet (die erste Messung benachrichtigt immer). Schreibe drei Beobachter: Display speichert display: 21-Zeilen in lines, Alarm(threshold) speichert alarm: 31 > 30-Zeilen in alerts für Temperaturen über dem Schwellenwert, und Stats gibt über summary() min 18, max 31, updates 4 zurück (oder no data).
Der bereitgestellte Code liest den Alarmschwellenwert und anschließend Temperaturen oder detach alarm / attach alarm ein, hängt zu Beginn alle drei Beobachter an und gibt schließlich die Display-Zeilen, die Alarme und die Zusammenfassung aus.
Dein Code gehört in WeatherStation.kt, Display.kt, Alarm.kt, Stats.kt und TemperatureObserver.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()
val station = WeatherStation()
val display = Display()
val alarm = Alarm(input[0].toInt())
val stats = Stats()
station.attach(display)
station.attach(alarm)
station.attach(stats)
for (cmd in input.drop(1)) {
when (cmd) {
"detach alarm" -> station.detach(alarm)
"attach alarm" -> station.attach(alarm)
else -> station.record(cmd.toInt())
}
}
for (line in display.lines) println(line)
for (line in alarm.alerts) println(line)
println(stats.summary())
}
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