Padrão State
Parte da seção Programação Orientada a Objetos do Journey de Kotlin da Coddy. Lição 49 de 57.
O padrão de estado dá a um objeto um comportamento diferente para cada estado em que ele se encontra. Cada estado é seu próprio tipo, e o objeto encaminha cada evento ao estado atual, que retorna o próximo:
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() }
}Dentro de main:
val lamp = Lamp()
repeat(4) {
lamp.press()
println(lamp.state.name)
}Saída:
dim
bright
off
dimUm estado pode carregar dados. Com uma interface selada, um data object representa um estado sem dados, uma classe de dados representa um estado com dados, e uma função retorna o próximo estado para cada 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 de main:
var spot: Parking = Free
spot = spot.arrive("AB12", 9)
println(spot)
val (next, fee) = spot.leave(12)
println("$next, fee $fee")Saída:
Taken(plate=AB12, since=9)
Free, fee 6Alguns eventos não são permitidos em determinados estados. A transição pode retornar null para eles; nesse caso, o objeto permanece onde está e informa a recusa:
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
}Dentro de 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")Saída:
lock refused while Open
open refused while Locked
final: ClosedPor que não usar algumas flags Boolean? Flags como isOpen e isLocked permitem combinações impossíveis, como estar aberta e trancada ao mesmo tempo, e cada método precisa verificá-las. Uma propriedade de estado contém exatamente um estado válido, e um when sobre um tipo selado faz o compilador verificar se todos os estados foram tratados:
// flags: 4 combinações, uma delas impossível
class DoorWithFlags {
var isOpen = false
var isLocked = false // isOpen && isLocked nunca deve acontecer
}
// state: sempre exatamente um de Open, Closed, Locked
class DoorWithState {
var state: Door = Closed
private set
}Desafio
FácilUma máquina de venda automática está em um dos estados fornecidos Idle, HasCoins(cents) ou SoldOut. Escreva Vending.label(), que retorna idle, coins 100 ou sold out, e a classe VendingMachine, cujo state começa como Idle e só pode ser alterado internamente. handle(event) aplica um evento e retorna true, ou retorna false e não altera nada quando o evento não é permitido: coin <n> vai de Idle para HasCoins(n) e adiciona ao valor em HasCoins; buy em HasCoins com pelo menos 150 centavos é uma venda (contabilizada em sales) e retorna a Idle; refund em HasCoins vai para Idle; empty vai para SoldOut a partir de qualquer estado; refill em SoldOut vai para Idle.
O código fornecido aplica os eventos em ordem e imprime o rótulo do estado após cada um, ou not allowed: <event>, e por fim sales: 1.
Seu código deve ficar em VendingMachine.kt, Label.kt, Vending.kt, Idle.kt, HasCoins.kt e SoldOut.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 machine = VendingMachine()
for (event in input) {
if (machine.handle(event)) println(machine.state.label()) else println("not allowed: $event")
}
println("sales: ${machine.sales}")
}
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