반디북
객체에서 함수로
Ch6
우석

연습문제 결과

 
sealed class ElevatorCommand {
    abstract val floor: Int
 
    data class Call(override val floor: Int) : ElevatorCommand()
    data class Select(override val floor: Int) : ElevatorCommand()
    data class Fix(override val floor: Int) : ElevatorCommand()
}
 
sealed class ElevatorState {
    abstract val startFloor: Int
 
    data class Moving(val toFloor: Int) : ElevatorState() {
        override val startFloor get() = toFloor
    }
 
    data class Stopped(val floor: Int) : ElevatorState() {
        override val startFloor get() = floor
    }
 
    data class Broken(val floor: Int) : ElevatorState() {
        override val startFloor get() = floor
    }
}
 
sealed class ElevatorEvent {
    data class ButtonPressed(val floor: Int) : ElevatorEvent()
    data class ElevatorMoved(val fromFloor: Int, val toFloor: Int) : ElevatorEvent()
    data class Broken(val floor: Int) : ElevatorEvent()
    data class Fixed(val floor: Int) : ElevatorEvent()
}
 
 
 
fun foldElevatorEvents(events: List<ElevatorEvent>): ElevatorState =
    events.fold(ElevatorState.Stopped(0) as ElevatorState) { state, event ->
        when (event) {
            is ElevatorEvent.Broken ->
                ElevatorState.Broken(state.startFloor)
            is ElevatorEvent.ButtonPressed ->
                if (state is ElevatorState.Broken) state
                else if (event.floor != state.startFloor) ElevatorState.Moving(event.floor)
                else ElevatorState.Stopped(event.floor)
            is ElevatorEvent.ElevatorMoved ->
                if (state is ElevatorState.Broken) state
                else ElevatorState.Stopped(event.toFloor)
            is ElevatorEvent.Fixed -> ElevatorState.Stopped(event.floor)
        }
    }
 
fun handleElevatorCommand(state: ElevatorState, command: ElevatorCommand): List<ElevatorEvent> =
    when (command) {
        is ElevatorCommand.Call, is ElevatorCommand.Select ->
            if (state is ElevatorState.Broken) listOf(ElevatorEvent.Broken(state.startFloor))
            else if (command.floor != state.startFloor) listOf(
                ElevatorEvent.ButtonPressed(command.floor),
                ElevatorEvent.ElevatorMoved(state.startFloor, command.floor)
            )
            else emptyList()
        is ElevatorCommand.Fix ->
            if (state is ElevatorState.Broken) listOf(ElevatorEvent.Fixed(state.startFloor))
            else emptyList()
    }

연습문제 6.1

@Test
fun `initially with no events it is stopped at floor 0`() {
    val state = foldElevatorEvents(emptyList())
    expectThat(state).isEqualTo(ElevatorState.Stopped(floor = 0))
}
 
@Test
fun `ButtonPressed for a different floor yields Moving state`() {
    val events = listOf(
        ElevatorEvent.ButtonPressed(floor = 3)
    )
    val state = foldElevatorEvents(events)
    expectThat(state).isEqualTo(ElevatorState.Moving(toFloor = 3))
}
 
@Test
fun `ButtonPressed for the same floor yields Stopped state`() {
    val events = listOf(
        ElevatorEvent.ButtonPressed(floor = 0)
    )
    val state = foldElevatorEvents(events)
    expectThat(state).isEqualTo(ElevatorState.Stopped(floor = 0))
}
 
@Test
fun `ElevatorMoved always yields Stopped at toFloor`() {
    val events = listOf(
        ElevatorEvent.ElevatorMoved(fromFloor = 5, toFloor = 2)
    )
    val state = foldElevatorEvents(events)
    expectThat(state).isEqualTo(ElevatorState.Stopped(floor = 2))
}
 
@Test
fun `mixed sequence of ButtonPressed and ElevatorMoved folds correctly`() {
    val events = listOf(
        ElevatorEvent.ButtonPressed(floor = 2),
        ElevatorEvent.ElevatorMoved(fromFloor = 0, toFloor = 2),
        ElevatorEvent.ButtonPressed(floor = 5),
        ElevatorEvent.ElevatorMoved(fromFloor = 2, toFloor = 5)
    )
    val state = foldElevatorEvents(events)
    expectThat(state).isEqualTo(ElevatorState.Stopped(floor = 5))
}

연습문제 6.2

@Test
fun `elevator opens the doors at the called floor`() {
    val events = listOf<ElevatorEvent>(ElevatorEvent.ElevatorMoved(fromFloor = 0, toFloor = 3))
    val newEvents = processCommand(events, ElevatorCommand.Call(floor = 5))
    expectThat(foldElevatorEvents(newEvents))
        .isEqualTo(ElevatorState.Stopped(floor = 5))
}
 
@Test
fun `elevator opens the doors at the selected floor when someone presses a floor button`() {
    val events = listOf<ElevatorEvent>(ElevatorEvent.ElevatorMoved(fromFloor = 0, toFloor = 5))
    val newEvents = processCommand(events, ElevatorCommand.Select(floor = 10))
    expectThat(foldElevatorEvents(newEvents))
        .isEqualTo(ElevatorState.Stopped(floor = 10))
}
 
@Test
fun `elevator doesn't move if someone selects the same floor it's already on`() {
    val events = listOf(
        ElevatorEvent.ButtonPressed(floor = 10),
        ElevatorEvent.ElevatorMoved(fromFloor = 0, toFloor = 10)
    )
    val newEvents = processCommand(events, ElevatorCommand.Select(floor = 10))
    expectThat(foldElevatorEvents(newEvents))
        .isEqualTo(ElevatorState.Stopped(floor = 10))
}
 
private fun processCommand(
    events: List<ElevatorEvent>,
    command: ElevatorCommand
): List<ElevatorEvent> =
    events + handleElevatorCommand(foldElevatorEvents(events), command)

연습문제 6.3

@Test
fun `if elevator is broken it won't move`() {
    val events = listOf<ElevatorEvent>(ElevatorEvent.Broken(0))
    val newEvents = events + handleElevatorCommand(
        foldElevatorEvents(events),
        ElevatorCommand.Call(5)
    )
    expectThat(foldElevatorEvents(newEvents)).isEqualTo(ElevatorState.Broken(0))
}
 
@Test
fun `if elevator is fixed change status`() {
 
    val events = listOf<ElevatorEvent>(ElevatorEvent.Broken(0))
    val newEvents = events + handleElevatorCommand(
        foldElevatorEvents(events),
        ElevatorCommand.Fix(0)
    )
    expectThat(foldElevatorEvents(newEvents)).isEqualTo(ElevatorState.Stopped(0))
}
만혁

연습문제 6.1

// ElevatorState.kt
sealed class ElevatorState
 
data class WaitingAt(val floor: Int) : ElevatorState() {
    fun handle(command: ElevatorCommand): ElevatorState = when (command) {
        is CallElevator -> MovingTo(to = command.floor, from = floor)
        is SelectFloor -> MovingTo(to = command.destination, from = floor)
    }
}
data class MovingTo(val to: Int, val from: Int): ElevatorState() {
    fun handle(command: ElevatorCommand) = this
}
 
 
// ElevatorCommand.kt
sealed class ElevatorCommand
 
data class CallElevator(val floor: Int): ElevatorCommand() 
data class SelectFloor(val destination: Int) : ElevatorCommand()
 
// Elevator.kt
 
fun handle(state: ElevatorState, command: ElevatorCommand): ElevatorState {
    return when (state) {
        is MovingTo -> state.handle(command)
        is WaitingAt -> state.handle(command)
    }
}
 
// ElevatorTest.kt
class ElevatorTest {
    @Test
    fun waitToMove() {
        val initState = WaitingAt(1)
        val command = SelectFloor(5)
 
        val handled = handle(initState, command)
 
        Assertions.assertEquals(MovingTo(5, 1), handled)
    }
}

연습문제 6.2

// ElevatorState.kt
sealed class ElevatorState
 
data class WaitingAt(val floor: Int) : ElevatorState() {
    fun commandToEvents(command: ElevatorCommand): List<ElevatorEvent> = when (command) {
        is CallElevator -> listOf(
            ElevatorCalled(floor = command.floor),
            ElevatorMoved(from = floor, to = command.floor)
        )
        is SelectFloor -> listOf(
            FloorSelected(command.destination),
            ElevatorMoved(to = command.destination, from = floor)
        )
    }
}
data class MovingTo(val to: Int, val from: Int): ElevatorState() {
    fun commandToEvents(command: ElevatorCommand) = this
}
 
 
// ElevatorCommand.kt
sealed class ElevatorCommand
 
data class CallElevator(val floor: Int): ElevatorCommand() 
data class SelectFloor(val destination: Int) : ElevatorCommand()
 
// ElevatorEvent.kt
sealed class ElevatorEvent
 
data class ElevatorMoved(val from: Int, val to: Int): ElevatorEvent()
data class ElevatorArrived(val floor: Int): ElevatorEvent()
data class FloorSelected(val floor: Int): ElevatorEvent()
data class ElevatorCalled(val floor: Int): ElevatorEvent()
 
 
 
// Elevator.kt
fun handleCommand(state: ElevatorState, command: ElevatorCommand) : List<ElevatorEvent> {
    return when(state) {
        is MovingTo -> emptyList()
        is WaitingAt -> {
            when(command) {
                is CallElevator -> state.commandToEvents(command)
                is SelectFloor -> state.commandToEvents(command)
            }
        }
    }
}
 
fun evolve(state: ElevatorState, event: ElevatorEvent): ElevatorState = when (event) {
    is ElevatorMoved -> MovingTo(to = event.to, from = event.from)
    is ElevatorArrived -> WaitingAt(event.floor)
    is FloorSelected -> state  // 상태 변화 없음
    is ElevatorCalled -> state  // 상태 변화 없음
}
 
fun fold(initial: ElevatorState, events: List<ElevatorEvent>): ElevatorState =
    events.fold(initial) { state, event -> evolve(state, event) }
 
 
// ElevatorTest.kt
class ElevatorTest {
    @Test
    fun addEvents() {
        val initState = WaitingAt(floor = 1)
        val events = listOf(
            ElevatorCalled(3),
            ElevatorMoved(from = 1, to = 3),
            ElevatorArrived(3),
            FloorSelected(5),
            ElevatorMoved(from = 3, to = 5),
            ElevatorArrived(floor = 5)
        )
 
        val result = fold(initState, events)
 
        Assertions.assertEquals(
            result, WaitingAt(floor = 5)
        )
    }
}

연습문제 6.3

// ElevatorState.kt
sealed class ElevatorState
 
data class WaitingAt(val floor: Int) : ElevatorState() {
    fun handle(command: ElevatorCommand): ElevatorState = when (command) {
        is CallElevator -> MovingTo(to = command.floor, from = floor)
        is SelectFloor -> MovingTo(to = command.destination, from = floor)
        is BreakDown -> MovingTo(to = 1, from = floor)
        is RepairElevator -> WaitingAt(floor = 1)
    }
 
    fun decide(command: ElevatorCommand): List<ElevatorEvent> = when (command) {
        is CallElevator -> listOf(
            ElevatorCalled(floor = command.floor),
            ElevatorMoved(from = floor, to = command.floor)
        )
 
        is SelectFloor -> listOf(
            FloorSelected(command.destination),
            ElevatorMoved(to = command.destination, from = floor)
        )
 
        is RepairElevator -> listOf(
            ElevatorRepaired(),
        )
 
        is BreakDown -> listOf(
            ElevatorMoved(from = floor, to = 1)
        )
    }
}
 
data class MovingTo(val to: Int, val from: Int) : ElevatorState() {
    fun handle(command: ElevatorCommand) = this
    fun decide(command: ElevatorCommand): List<ElevatorEvent> = emptyList()
}
 
data class Broken(val cause: String) : ElevatorState() {
    fun handle(command: ElevatorCommand): ElevatorState = this
    fun decide(command: ElevatorCommand): List<ElevatorEvent> = when (command) {
        is CallElevator, is SelectFloor, is BreakDown -> emptyList()
        is RepairElevator -> listOf(ElevatorRepaired())
    }
 
}
 
 
// ElevatorCommand.kt
data class CallElevator(val floor: Int): ElevatorCommand()
 
data class SelectFloor(val destination: Int) : ElevatorCommand()
 
data class BreakDown(val cause: String): ElevatorCommand()
 
class RepairElevator : ElevatorCommand()
 
 
 
 
// ElevatorEvent.kt
sealed class ElevatorEvent
 
data class ElevatorMoved(val from: Int, val to: Int): ElevatorEvent()
data class ElevatorArrived(val floor: Int): ElevatorEvent()
data class FloorSelected(val floor: Int): ElevatorEvent()
data class ElevatorCalled(val floor: Int): ElevatorEvent()
data class ElevatorBroken(val cause: String): ElevatorEvent()
class ElevatorRepaired : ElevatorEvent()
 
 
// Elevator.kt
fun handleCommand(state: ElevatorState, command: ElevatorCommand): List<ElevatorEvent> = when (state) {
    is MovingTo -> state.decide(command)
    is WaitingAt -> state.decide(command)
    is Broken -> state.decide(command)
}
 
fun evolve(state: ElevatorState, event: ElevatorEvent): ElevatorState = when (event) {
    is ElevatorMoved -> MovingTo(to = event.to, from = event.from)
    is ElevatorArrived -> WaitingAt(event.floor)
    is FloorSelected -> state  // 상태 변화 없음
    is ElevatorCalled -> state  // 상태 변화 없음
    is ElevatorBroken -> Broken(cause = event.cause)
    is ElevatorRepaired -> WaitingAt(floor = 1)
}
 
fun fold(initial: ElevatorState, events: List<ElevatorEvent>): ElevatorState =
    events.fold(initial) { state, event -> evolve(state, event) }
 
 
// ElevatorTest.kt
class ElevatorTest {
    @Test
    fun brokenTest() {
        val initState = WaitingAt(floor = 1)
        val events = listOf(
            ElevatorCalled(3),
            ElevatorMoved(from = 1, to = 3),
            ElevatorBroken("케이블 끊어짐"),
            ElevatorRepaired(),
        )
        val result = fold(initState, events)
 
        Assertions.assertEquals(
            result, WaitingAt(floor = 1)
        )
    }
}