Wzorzec obserwatora
Część sekcji Programowanie obiektowe ścieżki Kotlin w Coddy. Lekcja 44 z 57.
Wzorzec obserwatora pozwala obiektom subskrybować zdarzenia innego obiektu, podmiotu. Podmiot przechowuje listę obserwatorów i powiadamia każdego z nich, gdy coś się wydarzy, nie wiedząc, co z tym zrobią:
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")
}Wewnątrz main:
val acme = Stock("ACME")
acme.subscribe(Ticker())
acme.price = 120
acme.price = 95Wynik:
ACME is now 120
ACME is now 95W Kotlin obserwator jest często zwykłą funkcją. Podmiot przechowuje lambdy i wywołuje każdą z nich, więc nowy obserwator nie wymaga klasy:
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) }
}Wewnątrz main:
var clicks = 0
val save = Button("Save")
save.onClick { println("$it clicked") }
save.onClick { clicks++ }
save.click()
save.click()
println("clicks: $clicks")Wynik:
Save clicked
Save clicked
clicks: 2Obserwator może chcieć przestać nasłuchiwać. Jeśli subscribe zwraca funkcję, która ponownie usuwa obserwatora, wywołujący może anulować subskrypcję bez dostępu do listy obiektu obserwowanego:
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) }
}Wewnątrz 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")Wynik:
ada reads: hello
bo reads: hello
bo reads: againBiblioteka standardowa udostępnia właściwość obserwowalną. Delegates.observable(initial) { property, old, new -> ... } uruchamia lambdę po każdym przypisaniu; w rozdziale 10 wyjaśniono używane w niej słowo kluczowe by:
import kotlin.properties.Delegates
class Thermostat {
var target: Int by Delegates.observable(20) { _, old, new ->
println("target changed from $old to $new")
}
}Wewnątrz main:
val t = Thermostat()
t.target = 22
t.target = 18Wynik:
target changed from 20 to 22
target changed from 22 to 18Wyzwanie
ŁatwyObiekt WeatherStation powiadamia obserwatorów implementujących dostarczony interfejs TemperatureObserver. Napisz metody attach(observer), detach(observer) i record(temp), które powiadamiają każdego dołączonego obserwatora, ale tylko wtedy, gdy temperatura różni się od poprzedniego odczytu (pierwszy odczyt zawsze powoduje powiadomienie). Napisz trzech obserwatorów: Display przechowuje w lines wiersze display: 21, Alarm(threshold) przechowuje w alerts wiersze alarm: 31 > 30 dla temperatur powyżej progu, a Stats zwraca z summary() wartość min 18, max 31, updates 4 (lub no data).
Dostarczony kod odczytuje próg alarmu, a następnie temperatury lub polecenia detach alarm / attach alarm, na początku dołącza wszystkich trzech obserwatorów, a na końcu wyświetla wiersze z wyświetlacza, alerty i podsumowanie.
Umieść swój kod w plikach WeatherStation.kt, Display.kt, Alarm.kt, Stats.kt i TemperatureObserver.kt. Plik Main.kt zawiera dostarczony kod wejścia/wyjścia i nie można go edytować.
Spróbuj swoich sił
fun main() {
// Dostarczony kod wejścia/wyjścia: pozostaw go bez zmian
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())
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Klasy i obiekty
Praca z plikamiDefiniowanie klasKonstruktory główneBloki init i walidacjaKonstruktory dodatkowePodsumowanie — karta biblioteczna4Dziedziczenie
Klasy otwarte i podklasyNadpisywanie składowychsuper i konstruktoryKlasy abstrakcyjnePodsumowanie – katalog pracowników7Klasy specjalne
Klasy danychKlasy wyliczenioweHierarchie zapieczętowaneequals, hashCode, toStringPodsumowanie — śledzenie zamówień10Wzorce projektowe, część 2
Delegowanie za pomocą byWzorzec dekoratoraWzorzec budowniczegoWzorzec stanu2Właściwości i hermetyzacja
Własne akcesoryModyfikatory widocznościPrywatne setterylazy i lateinitPowtórka – konto bankowe5Interfejsy
Deklarowanie interfejsówMetody domyślneWiele interfejsówInterfejsy jako typyPowtórzenie – metody płatności8Typy generyczne
Funkcje generyczneKlasy generyczneOgraniczenia typówKowariancja i kontrawariancja: in i outPodsumowanie — kolejka priorytetowa11Projekt: Zarządzanie biblioteką
Książki i czytelnicyWypożyczanie książekPoćwicz samodzielnie: Edytor online Kotlin