Menu
Coddy logo textTech

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 = 95

Output:

ACME is now 120
ACME is now 95

In 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: 2

Un 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: again

La 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 = 18

Output:

target changed from 20 to 22
target changed from 22 to 18
challenge icon

Sfida

Facile

Un 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())
}
quiz iconMettiti alla prova

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

Esercitati da solo: Playground Kotlin