Índice de temario
Día | Tema a tratar |
---|---|
1 | Definiendo objetivos |
2 | Proyecto inicial y value objects |
3 | Tratando con excepciones |
4 | Validando con Enum y parametrized test |
5 | Ampliando uso del Enum |
6 | Entidades |
Día 5. Agregando funcionalidad al Enum
En el post anterior, os comentaba lo fácil que es agregar información al Enum. Aparte de contexto, podemos agregarle valores para darle funcionalidad.
Al final, agregando funcionalidades al mower orientation, necesitábamos agregar direccionalidad en los ejes de desplazamiento para saber si moverse debía sumar valores (N, E), o restar valores (S, W).
Los tests y la interacción con el objeto MowerPosition nos llevaron a este punto:
package mower.mower.domain.value_object
import mower.mower.domain.exception.InvalidOrientationException
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource
import org.junit.jupiter.params.provider.ValueSource
import java.util.stream.Stream
import kotlin.test.Test
private const val ORIENTATION: String = "N"
private const val INVALID_ORIENTATION: String = "H"
internal class MowerOrientationTest
{
@Test
fun `Should be build`() {
val orientation = MowerOrientation.build(ORIENTATION)
assertInstanceOf(MowerOrientation::class.java, orientation)
assertThat(orientation.value).isEqualTo(ORIENTATION)
}
@ParameterizedTest(name = "{index} => orientation = ''{0}''")
@ValueSource(strings = [ "N", "S", "E", "W" ])
fun `Should be build with valid orientations`(value: String) {
val orientation = MowerOrientation.build(value)
assertThat(orientation.value).isEqualTo(value)
}
@Test
fun `Should throw exception for invalid Orientation`() {
assertThrows(InvalidOrientationException::class.java) {
MowerOrientation.build(INVALID_ORIENTATION)
}
}
@ParameterizedTest(name = "{index} => orientation = ''{0}'', movement = ''{1}'', result = ''{2}''")
@MethodSource("orientationAndMovementProvider")
fun `Should apply orientation movements`(orientationData: String, movementData: String, expectedOrientationData: String) {
var orientation = MowerOrientation.build(orientationData)
val movement = MowerMovement.build(movementData)
val expectedOrientation = MowerOrientation.build(expectedOrientationData)
orientation = orientation.changeOrientation(movement)
assertThat(orientation.value).isEqualTo(expectedOrientation.value)
}
@ParameterizedTest
@MethodSource("orientationAndYAxisAffectationProvider")
fun `Should eval if affects Y axis`(orientationData: String, affectsYAxis: Boolean) {
val orientation = MowerOrientation.build(orientationData)
assertThat(orientation.affectsYAxis()).isEqualTo(affectsYAxis)
}
@ParameterizedTest
@MethodSource("orientationAndXAxisAffectationProvider")
fun `Should eval if affects X axis`(orientationData: String, affectsXAxis: Boolean) {
val orientation = MowerOrientation.build(orientationData)
assertThat(orientation.affectsXAxis()).isEqualTo(affectsXAxis)
}
@ParameterizedTest
@MethodSource("orientationAndStepDirectionProvider")
fun `Should eval direction step` (orientationData: String, expectedStepDirection: Int) {
val orientation = MowerOrientation.build(orientationData)
assertThat(orientation.stepMovement()).isEqualTo(expectedStepDirection)
}
companion object {
@JvmStatic
fun orientationAndMovementProvider(): Stream<Arguments> {
return Stream.of(
Arguments.arguments("N", "F", "N"),
Arguments.arguments("N", "R", "E"),
Arguments.arguments("E", "R", "S"),
Arguments.arguments("S", "R", "W"),
Arguments.arguments("W", "R", "N"),
Arguments.arguments("N", "L", "W"),
Arguments.arguments("W", "L", "S"),
Arguments.arguments("S", "L", "E"),
Arguments.arguments("E", "L", "N")
)
}
@JvmStatic
fun orientationAndYAxisAffectationProvider(): Stream<Arguments> {
return Stream.of(
Arguments.arguments("N", true),
Arguments.arguments("S", true),
Arguments.arguments("E", false),
Arguments.arguments("W", false)
)
}
@JvmStatic
fun orientationAndXAxisAffectationProvider(): Stream<Arguments> {
return Stream.of(
Arguments.arguments("N", false),
Arguments.arguments("S", false),
Arguments.arguments("E", true),
Arguments.arguments("W", true)
)
}
@JvmStatic
fun orientationAndStepDirectionProvider(): Stream<Arguments> {
return Stream.of(
Arguments.arguments("N", 1),
Arguments.arguments("E", 1),
Arguments.arguments("S", -1),
Arguments.arguments("W", -1),
)
}
}
}
La clase que lo implementa nos quedó así…
@JvmInline
value class MowerOrientation private constructor(val value: String) {
private enum class Compass (val stepDirection: Int){
N (POSITIVE_DIRECTION),
E (POSITIVE_DIRECTION),
S (NEGATIVE_DIRECTION),
W (NEGATIVE_DIRECTION)
}
init {
try {
Compass.valueOf(value)
} catch (exception: IllegalArgumentException) {
throw InvalidOrientationException.withValues(value, Compass.values().contentToString())
}
}
companion object {
private const val COMPASS_STEP: Int = 1
private const val POSITIVE_DIRECTION: Int = 1
private const val NEGATIVE_DIRECTION: Int = -1
@JvmStatic
fun build(value: String): MowerOrientation
{
return MowerOrientation(value)
}
}
fun changeOrientation(mowerMovement: MowerMovement): MowerOrientation {
val currentCompass = Compass.valueOf(value)
if (mowerMovement.isClockWise()) {
val futureCompass = Compass.values().getOrElse(currentCompass.ordinal + COMPASS_STEP) { Compass.N }
return MowerOrientation(futureCompass.name)
}
if (mowerMovement.isCounterClockWise()) {
val futureCompass = Compass.values().getOrElse(currentCompass.ordinal - COMPASS_STEP) { Compass.W }
return MowerOrientation(futureCompass.name)
}
return this
}
fun affectsYAxis(): Boolean {
return Compass.N.name == value || Compass.S.name == value
}
fun affectsXAxis(): Boolean {
return Compass.E.name == value || Compass.W.name == value
}
fun stepMovement(): Int {
return Compass.valueOf(value).stepDirection
}
}
Posible refactor
Podríamos agregarle caraterísticas al Enum para simplificar las funciones affectsYAxis, affectsXAxis de la misma forma que está articulado con la función stepMovement. Dejando la clase con estos cambios.
...
@JvmInline
value class MowerOrientation private constructor(val value: String) {
private enum class Compass (val stepDirection: Int, val affectsXAxis: Boolean, val affectsYAxis: Boolean){
N(POSITIVE_DIRECTION, NOT_X_AXIS, Y_AXIS),
E(POSITIVE_DIRECTION, X_AXIS, NOT_Y_AXIS),
S(NEGATIVE_DIRECTION, NOT_X_AXIS, Y_AXIS),
W(NEGATIVE_DIRECTION, X_AXIS, NOT_Y_AXIS)
}
...
companion object {
...
private const val NOT_X_AXIS: Boolean = false
private const val NOT_Y_AXIS: Boolean = false
private const val X_AXIS: Boolean = true
private const val Y_AXIS: Boolean = true
...
}
...
fun affectsYAxis(): Boolean {
return Compass.valueOf(value).affectsYAxis
}
fun affectsXAxis(): Boolean {
return Compass.valueOf(value).affectsXAxis
}
fun stepMovement(): Int {
return Compass.valueOf(value).stepDirection
}
}
Share this post
Twitter
Facebook
Reddit
LinkedIn
Email