Pattern Observer
Fa parte della sezione Programmazione orientata agli oggetti del percorso Kotlin di Coddy. Lezione 44 di 57.
Il pattern observer permette agli oggetti di iscriversi agli eventi di un altro oggetto, il subject. Il subject mantiene un elenco di observer e notifica ciascuno di essi quando accade qualcosa, senza sapere cosa ne fanno:
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")
}All'interno di main:
val acme = Stock("ACME")
acme.subscribe(Ticker())
acme.price = 120
acme.price = 95Output:
ACME is now 120
ACME is now 95In Kotlin un osservatore è spesso una semplice funzione. Il soggetto memorizza le lambda e le richiama una per una, quindi per un nuovo osservatore non serve alcuna classe:
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) }
}All'interno di main:
var clicks = 0
val save = Button("Save")
save.onClick { println("$it clicked") }
save.onClick { clicks++ }
save.click()
save.click()
println("clicks: $clicks")Output:
Save clicked
Save clicked
clicks: 2Un osservatore potrebbe voler smettere di ascoltare. Se subscribe restituisce una funzione che rimuove di nuovo l'osservatore, il chiamante può annullare senza accedere all'elenco del soggetto:
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) }
}All'interno di 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")Output:
ada reads: hello
bo reads: hello
bo reads: againLa libreria standard offre una proprietà osservabile. Delegates.observable(initial) { property, old, new -> ... } esegue la lambda dopo ogni assegnazione; il capitolo 10 spiega la parola chiave by che usa:
import kotlin.properties.Delegates
class Thermostat {
var target: Int by Delegates.observable(20) { _, old, new ->
println("target changed from $old to $new")
}
}All'interno di main:
val t = Thermostat()
t.target = 22
t.target = 18Output:
target changed from 20 to 22
target changed from 22 to 18Sfida
FacileUn WeatherStation notifica gli osservatori che implementano TemperatureObserver, fornito. Scrivi attach(observer), detach(observer) e record(temp), che notifica ogni osservatore collegato, ma solo quando la temperatura è diversa dalla lettura precedente (la prima lettura notifica sempre). Scrivi tre osservatori: Display conserva le righe display: 21 in lines, Alarm(threshold) conserva le righe alarm: 31 > 30 in alerts per le temperature superiori alla soglia e Stats restituisce min 18, max 31, updates 4 da summary() (oppure no data).
Il codice fornito legge la soglia dell'allarme e poi le temperature o detach alarm / attach alarm, collega tutti e tre gli osservatori all'inizio e infine stampa le righe del display, gli avvisi e il riepilogo.
Il tuo codice va inserito in WeatherStation.kt, Display.kt, Alarm.kt, Stats.kt e TemperatureObserver.kt. Main.kt contiene il codice di input/output fornito e non può essere modificato.
Provalo tu
fun main() {
// Codice di input/output fornito: lascialo così com'è
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())
}
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