Menu
Coddy logo textTech

Durum Deseni

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

state deseni, bir nesneye içinde bulunduğu her durum için farklı bir davranış kazandırır. Her durum kendi türüdür ve nesne her olayı mevcut durumuna iletir; mevcut durum da bir sonraki durumu döndürür:

interface LampState {
    val name: String
    fun press(): LampState
}
object Off : LampState {
    override val name = "off"
    override fun press() = Dim
}
object Dim : LampState {
    override val name = "dim"
    override fun press() = Bright
}
object Bright : LampState {
    override val name = "bright"
    override fun press() = Off
}

class Lamp {
    var state: LampState = Off
        private set
    fun press() { state = state.press() }
}

main içinde:

val lamp = Lamp()
repeat(4) {
    lamp.press()
    println(lamp.state.name)
}

Çıktı:

dim
bright
off
dim

Bir durum veri taşıyabilir. Mühürlü bir arayüz ile data object verisiz bir durumu, bir veri sınıfı verili bir durumu temsil eder ve bir işlev her olay için bir sonraki durumu döndürür:

sealed interface Parking
data object Free : Parking
data class Taken(val plate: String, val since: Int) : Parking

fun Parking.arrive(plate: String, time: Int): Parking = when (this) {
    Free -> Taken(plate, time)
    is Taken -> this
}

fun Parking.leave(time: Int): Pair<Parking, Int> = when (this) {
    Free -> this to 0
    is Taken -> Free to (time - since) * 2
}

main içinde:

var spot: Parking = Free
spot = spot.arrive("AB12", 9)
println(spot)
val (next, fee) = spot.leave(12)
println("$next, fee $fee")

Çıktı:

Taken(plate=AB12, since=9)
Free, fee 6

Bazı olaylara bazı durumlarda izin verilmez. Geçiş, bunlar için null döndürebilir; nesne bulunduğu yerde kalır ve reddedildiğini bildirir:

sealed interface Door
data object Open : Door
data object Closed : Door
data object Locked : Door

fun next(door: Door, event: String): Door? = when (door) {
    Open -> if (event == "close") Closed else null
    Closed -> when (event) { "open" -> Open; "lock" -> Locked; else -> null }
    Locked -> if (event == "unlock") Closed else null
}

main içinde:

var door: Door = Open
for (e in listOf("lock", "close", "lock", "open", "unlock")) {
    val n = next(door, e)
    if (n == null) println("$e refused while $door") else door = n
}
println("final: $door")

Çıktı:

lock refused while Open
open refused while Locked
final: Closed

Neden birkaç Boolean bayrağı kullanmayalım? isOpen ve isLocked gibi bayraklar, aynı anda açık ve kilitli olmak gibi imkânsız birleşimlere izin verir ve her yöntemin bunları kontrol etmesi gerekir. Tek bir durum özelliği tam olarak bir geçerli durum tutar ve mühürlü bir tür üzerinde kullanılan when, derleyicinin her durumun ele alındığını kontrol etmesini sağlar:

// flags: 4 kombinasyon, bunlardan biri imkânsız
class DoorWithFlags {
    var isOpen = false
    var isLocked = false        // isOpen && isLocked asla gerçekleşmemeli
}
// state: her zaman tam olarak Open, Closed, Locked'dan biri
class DoorWithState {
    var state: Door = Closed
        private set
}
challenge icon

Görev

Kolay

Bir satış makinesi, sağlanan Idle, HasCoins(cents) veya SoldOut durumlarından birindedir. idle, coins 100 veya sold out döndüren Vending.label() işlevini ve state değeri Idle olarak başlayan ve yalnızca sınıfın içinde değiştirilebilen VendingMachine sınıfını yazın. handle(event) bir olayı uygular ve true döndürür; olaya izin verilmiyorsa hiçbir şeyi değiştirmeden false döndürür: coin <n>, Idle durumundan HasCoins(n) durumuna geçer ve HasCoins içindeki miktara ekleme yapar; HasCoins durumunda en az 150 sent ile buy bir satıştır (sales içinde sayılır) ve yeniden Idle durumuna geçer; HasCoins durumunda refund, Idle durumuna geçer; empty, herhangi bir durumdan SoldOut durumuna geçer; SoldOut durumunda refill, Idle durumuna geçer.

Sağlanan kod, olayları sırayla uygular ve her olaydan sonra durumun etiketini ya da not allowed: <event> yazdırır; son olarak sales: 1 yazdırır.

Kodunuz VendingMachine.kt, Label.kt, Vending.kt, Idle.kt, HasCoins.kt ve SoldOut.kt dosyalarına yazılmalıdır. Main.kt, sağlanan girdi/çıktı 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 machine = VendingMachine()
    for (event in input) {
        if (machine.handle(event)) println(machine.state.label()) else println("not allowed: $event")
    }
    println("sales: ${machine.sales}")
}
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