Menu
Coddy logo textTech

Gözlemci Kalıbı

Coddy'nin Kotlin Journey'sinin Nesne Yönelimli Programlama bölümünün bir parçası. Ders 44 / 57.

gözlemci deseni, nesnelerin başka bir nesnenin, yani öznenin, olaylarına abone olmasını sağlar. Özne, gözlemcilerin bir listesini tutar ve ne yaptıklarını bilmeden bir şey olduğunda her birini bilgilendirir:

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")
}

main içinde:

val acme = Stock("ACME")
acme.subscribe(Ticker())
acme.price = 120
acme.price = 95

Çıktı:

ACME is now 120
ACME is now 95

Kotlin'de bir gözlemci genellikle düz bir işlevdir. Özne lambdaları saklar ve her birini çağırır; bu nedenle yeni bir gözlemcinin sınıfa ihtiyacı yoktur:

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) }
}

main içinde:

var clicks = 0
val save = Button("Save")
save.onClick { println("$it clicked") }
save.onClick { clicks++ }
save.click()
save.click()
println("clicks: $clicks")

Çıktı:

Save clicked
Save clicked
clicks: 2

Bir gözlemci dinlemeyi bırakmak isteyebilir. subscribe, gözlemciyi yeniden kaldıran bir işlev döndürürse çağıran, öznenin listesine hiç erişmeden iptal edebilir:

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) }
}

main içinde:

val feed = Feed()
val cancel = feed.subscribe { println("ada reads: $it") }
feed.subscribe { println("bo reads: $it") }
feed.post("hello")
cancel()
feed.post("again")

Çıktı:

ada reads: hello
bo reads: hello
bo reads: again

Standart kitaplık, gözlemlenebilir bir özelliğe sahiptir. Delegates.observable(initial) { property, old, new -> ... } her atamadan sonra lambda'yı çalıştırır; 10. bölüm, kullandığı by anahtar sözcüğünü açıklar:

import kotlin.properties.Delegates

class Thermostat {
    var target: Int by Delegates.observable(20) { _, old, new ->
        println("target changed from $old to $new")
    }
}

main içinde:

val t = Thermostat()
t.target = 22
t.target = 18

Çıktı:

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

Görev

Kolay

Bir WeatherStation, sağlanan TemperatureObserver arayüzünü uygulayan gözlemcileri bilgilendirir. Her bağlı gözlemciyi bilgilendiren, ancak yalnızca sıcaklık önceki okumadan farklı olduğunda (ilk okuma her zaman bilgilendirir) attach(observer), detach(observer) ve record(temp) işlevlerini yazın. Üç gözlemci yazın: Display, lines içinde display: 21 satırlarını tutar; Alarm(threshold), eşik değerinin üzerindeki sıcaklıklar için alerts içinde alarm: 31 > 30 satırlarını tutar; Stats ise summary() işlevinden min 18, max 31, updates 4 (veya no data) döndürür.

Sağlanan kod alarm eşiğini ve ardından sıcaklıkları veya detach alarm / attach alarm komutlarını okur, başlangıçta üç gözlemcinin tümünü bağlar ve son olarak görüntü satırlarını, uyarıları ve özeti yazdırır.

Kodunuz WeatherStation.kt, Display.kt, Alarm.kt, Stats.kt ve TemperatureObserver.kt dosyalarına gider. Main.kt, sağlanan giriş/çıkış kodunu içerir ve düzenlenemez.

Kendin dene

fun main() {
    // Sağlanan girdi/çıktı kodu: olduğu gibi bırakın
    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 iconKendini test et

Bu ders kısa bir quiz içerir. Soruları yanıtlamak ve ilerlemeni kaydetmek için derse başla.

Nesne Yönelimli Programlama bölümündeki tüm dersler

Kendi başına pratik yap: Kotlin playground