Padrão Observer
Parte da seção Programação Orientada a Objetos do Journey de Kotlin da Coddy. Lição 44 de 57.
O padrão observer permite que objetos se inscrevam nos eventos de outro objeto, o subject. O subject mantém uma lista de observers e notifica cada um quando algo acontece, sem saber o que eles fazem com isso:
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")
}Dentro de main:
val acme = Stock("ACME")
acme.subscribe(Ticker())
acme.price = 120
acme.price = 95Saída:
ACME is now 120
ACME is now 95Em Kotlin, um observador geralmente é uma função simples. O sujeito armazena lambdas e chama cada uma delas, portanto um novo observador não precisa de uma 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) }
}Dentro de main:
var clicks = 0
val save = Button("Save")
save.onClick { println("$it clicked") }
save.onClick { clicks++ }
save.click()
save.click()
println("clicks: $clicks")Saída:
Save clicked
Save clicked
clicks: 2Um observador pode querer parar de escutar. Se subscribe retornar uma função que remove o observador novamente, o chamador poderá cancelar sem ter acesso à lista do assunto:
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) }
}Dentro de 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")Saída:
ada reads: hello
bo reads: hello
bo reads: againA biblioteca padrão tem uma propriedade observável. Delegates.observable(initial) { property, old, new -> ... } executa a lambda após cada atribuição; o capítulo 10 explica a palavra-chave by que ela usa:
import kotlin.properties.Delegates
class Thermostat {
var target: Int by Delegates.observable(20) { _, old, new ->
println("target changed from $old to $new")
}
}Dentro de main:
val t = Thermostat()
t.target = 22
t.target = 18Saída:
target changed from 20 to 22
target changed from 22 to 18Desafio
FácilUm WeatherStation notifica observadores que implementam o TemperatureObserver fornecido. Escreva attach(observer), detach(observer) e record(temp), que notifica todos os observadores anexados, mas somente quando a temperatura difere da leitura anterior (a primeira leitura sempre notifica). Escreva três observadores: Display mantém linhas display: 21 em lines, Alarm(threshold) mantém linhas alarm: 31 > 30 em alerts para temperaturas acima do limite, e Stats retorna min 18, max 31, updates 4 a partir de summary() (ou no data).
O código fornecido lê o limite do alarme e, em seguida, temperaturas ou detach alarm / attach alarm, anexa os três observadores no início e, por fim, imprime as linhas do display, os alertas e o resumo.
Seu código deve estar em WeatherStation.kt, Display.kt, Alarm.kt, Stats.kt e TemperatureObserver.kt. Main.kt contém o código fornecido de entrada/saída e não pode ser editado.
Experimente você mesmo
fun main() {
// Código de entrada/saída fornecido: mantenha-o como está
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())
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Classes e Objetos
Trabalhando com ArquivosDefinindo ClassesConstrutores PrimáriosBlocos init e ValidaçãoConstrutores SecundáriosRecapitulação - Cartão da Biblioteca4Herança
Classes Abertas e SubclassesSobrescrevendo Membrossuper e ConstrutoresClasses AbstratasRevisão - Diretório de Funcionários2Propriedades e Encapsulamento
Acessores PersonalizadosModificadores de VisibilidadeSetters Privadoslazy e lateinitRevisão - Conta Bancária5Interfaces
Declarando InterfacesMétodos PadrãoMúltiplas InterfacesInterfaces como TiposRecapitulação - Métodos de Pagamento8Genéricos
Funções genéricasClasses genéricasRestrições de tipoVariância com in e outRecapitulação - Fila de prioridade11Projeto: Gerenciamento de Biblioteca
Livros e MembrosEmpréstimo de LivrosPratique por conta própria: Playground de Kotlin