Patron Observer
Fait partie de la section Programmation orientée objet du Journey Kotlin de Coddy. Leçon 44 sur 57.
Le motif observateur permet aux objets de s’abonner aux événements d’un autre objet, le sujet. Le sujet conserve une liste d’observateurs et informe chacun d’eux lorsqu’un événement se produit, sans savoir ce qu’ils en font :
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")
}Dans main :
val acme = Stock("ACME")
acme.subscribe(Ticker())
acme.price = 120
acme.price = 95Sortie :
ACME is now 120
ACME is now 95En Kotlin, un observateur est souvent une simple fonction. Le sujet stocke des lambdas et appelle chacune d’elles, donc un nouvel observateur n’a besoin d’aucune 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) }
}Dans main :
var clicks = 0
val save = Button("Save")
save.onClick { println("$it clicked") }
save.onClick { clicks++ }
save.click()
save.click()
println("clicks: $clicks")Sortie :
Save clicked
Save clicked
clicks: 2Un observateur peut vouloir cesser d’écouter. Si subscribe renvoie une fonction qui supprime à nouveau l’observateur, l’appelant peut annuler sans avoir accès à la liste du sujet :
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) }
}Dans 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")Sortie :
ada reads: hello
bo reads: hello
bo reads: againLa bibliothèque standard possède une propriété observable. Delegates.observable(initial) { property, old, new -> ... } exécute le lambda après chaque affectation ; le chapitre 10 explique le mot-clé by qu’il utilise :
import kotlin.properties.Delegates
class Thermostat {
var target: Int by Delegates.observable(20) { _, old, new ->
println("target changed from $old to $new")
}
}À l’intérieur de main :
val t = Thermostat()
t.target = 22
t.target = 18Sortie :
target changed from 20 to 22
target changed from 22 to 18Défi
FacileUn WeatherStation notifie les observateurs qui implémentent le TemperatureObserver fourni. Écrivez attach(observer), detach(observer) et record(temp), qui notifient chaque observateur attaché, mais uniquement lorsque la température diffère de la lecture précédente (la première lecture notifie toujours). Écrivez trois observateurs : Display conserve les lignes display: 21 dans lines, Alarm(threshold) conserve dans alerts les lignes alarm: 31 > 30 pour les températures supérieures au seuil, et Stats renvoie min 18, max 31, updates 4 depuis summary() (ou no data).
Le code fourni lit le seuil de l’alarme, puis des températures ou detach alarm / attach alarm, attache les trois observateurs au début et affiche finalement les lignes de l’affichage, les alertes et le résumé.
Votre code doit être placé dans WeatherStation.kt, Display.kt, Alarm.kt, Stats.kt et TemperatureObserver.kt. Main.kt contient le code d’entrée/sortie fourni et ne peut pas être modifié.
Essayez vous-même
fun main() {
// Code d'entrée/sortie fourni : le garder tel quel
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())
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Programmation orientée objet
1Classes et objets
Travailler avec des fichiersDéfinir des classesConstructeurs primairesBlocs init et validationConstructeurs secondairesRécapitulatif - Carte de bibliothèque4Héritage
Classes ouvertes et sous-classesRedéfinition des membressuper et constructeursClasses abstraitesRécapitulatif – Annuaire du personnel2Propriétés et encapsulation
Accesseurs personnalisésModificateurs de visibilitéSetters privéslazy et lateinitRécapitulatif - Compte bancaire5Interfaces
Déclarer des interfacesMéthodes par défautInterfaces multiplesInterfaces en tant que typesRécapitulatif - Méthodes de paiement8Génériques
Fonctions génériquesClasses génériquesContraintes de typeVariance avec in et outRécapitulatif - File de priorité11Projet : Gestion de bibliothèque
Livres et membresEmprunter des livresEntraînez-vous par vous-même : Playground Kotlin