Pattern State
Fa parte della sezione Programmazione orientata agli oggetti del percorso Kotlin di Coddy. Lezione 49 di 57.
Il pattern state assegna a un oggetto un comportamento diverso per ogni stato in cui si trova. Ogni stato è un tipo a sé e l’oggetto inoltra ogni evento al proprio stato corrente, che restituisce quello successivo:
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() }
}All’interno di main:
val lamp = Lamp()
repeat(4) {
lamp.press()
println(lamp.state.name)
}Output:
dim
bright
off
dimUno stato può contenere dati. Con un'interfaccia sealed, un data object rappresenta uno stato senza dati, una classe dati uno stato con dati e una funzione restituisce lo stato successivo per ogni evento:
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
}Dentro main:
var spot: Parking = Free
spot = spot.arrive("AB12", 9)
println(spot)
val (next, fee) = spot.leave(12)
println("$next, fee $fee")Output:
Taken(plate=AB12, since=9)
Free, fee 6Alcuni eventi non sono consentiti in alcuni stati. Per questi, la transizione può restituire null e l’oggetto rimane dov’è e segnala il rifiuto:
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
}All’interno di main:
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")Output:
lock refused while Open
open refused while Locked
final: ClosedPerché non usare alcuni flag Boolean? Flag come isOpen e isLocked consentono combinazioni impossibili, come essere aperto e bloccato allo stesso tempo, e ogni metodo deve controllarli. Una proprietà di stato contiene esattamente uno stato valido, e un when su un tipo sealed fa sì che il compilatore controlli che vengano gestiti tutti gli stati:
// flag: 4 combinazioni, una delle quali impossibile
class DoorWithFlags {
var isOpen = false
var isLocked = false // isOpen && isLocked non deve mai verificarsi
}
// stato: sempre esattamente uno tra Open, Closed, Locked
class DoorWithState {
var state: Door = Closed
private set
}Sfida
FacileUna macchinetta automatica si trova in uno degli stati forniti Idle, HasCoins(cents) o SoldOut. Scrivi Vending.label(), che restituisce idle, coins 100 o sold out, e la classe VendingMachine, il cui state inizia come Idle e può essere modificato solo al suo interno. handle(event) applica un evento e restituisce true, oppure restituisce false e non modifica nulla quando l'evento non è consentito: coin <n> passa da Idle a HasCoins(n) e aggiunge l'importo a quello in HasCoins; buy in HasCoins con almeno 150 centesimi costituisce una vendita (viene conteggiata in sales) e riporta a Idle; refund in HasCoins passa a Idle; empty passa a SoldOut da qualsiasi stato; refill in SoldOut passa a Idle.
Il codice fornito applica gli eventi in ordine e stampa l'etichetta dello stato dopo ciascuno, oppure not allowed: <event>, e infine sales: 1.
Il tuo codice va inserito in VendingMachine.kt, Label.kt, Vending.kt, Idle.kt, HasCoins.kt e SoldOut.kt. Main.kt contiene il codice fornito per l'input/output e non può essere modificato.
Provalo tu
fun main() {
// Codice di input/output fornito: lascialo così com'è
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}")
}
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
1Classi e oggetti
Lavorare con i fileDefinire le classiCostruttori primariBlocchi init e convalidaCostruttori secondariRiepilogo - Tessera della biblioteca4Ereditarietà
Classi aperte e sottoclassiRidefinizione dei membrisuper e costruttoriClassi astratteRiepilogo - Elenco del personale2Proprietà e incapsulamento
Accessor personalizzatiModificatori di visibilitàSetter privatilazy e lateinitRipasso - Conto bancario5Interfacce
Dichiarare le interfacceMetodi predefinitiInterfacce multipleLe interfacce come tipiRiepilogo - Metodi di pagamento8Tipi generici
Funzioni genericheClassi genericheVincoli sui tipiVarianza con in e outRiepilogo - Coda con priorità11Progetto: Gestione della biblioteca
Libri e sociPrendere in prestito i libriEsercitati da solo: Playground Kotlin