2012-06-25 12 views
6

Estoy intentando modelar las respuestas de las API REST como clases de caso en las que puedo usar la coincidencia de patrones.Modelado con la clase de caso Scala

Pensé que sería una buena opción asumir la herencia, pero veo que esto está en desuso. Sé que ya hay preguntas relacionadas con las clases de casos y la herencia, pero mi pregunta es más acerca de cómo modelaría el siguiente la "forma correcta" aquí sin herencia.

Empecé con las dos clases de casos siguientes, que funcionan muy bien:

case class Body(contentType: String, content: String) 
case class Response(statusCode: Int, body: Body) 

es decir, una llamada REST volvería con algo como:

Response(200, Body("application/json", """{ "foo": "bar" }""")) 

cual pude coincidencia de patrones como:

response match { 
    case Response(200, Body("application/json", json)) => println(json) 
    case Response(200, Body("text/xml", xml)) => println(xml) 
    case Response(_,_) => println("Something unexpected") 
} 

etc. que funciona bien.

Cuando me encontré con problemas es: Me gustaría extensiones de ayuda para estas clases de casos, tales como:

case class OK(body: Body) extends Response(200, body) 
case class NotFound() extends Response(404, Body("text/plain", "Not Found")) 

case class JSON(json: String) extends Body("application/json", json) 
case class XML(xml: String) extends Body("text/xml", xml) 

para que yo puedo hacer simplificado patrón coincide así:

response match { 
    case OK(JSON(json)) => println(json) 
    case OK(XML(xml)) => println(xml) 
    case NotFound() => println("Something is not there") 

    // And still drop down to this if necessary: 
    case Response(302, _) => println("It moved") 
} 

y que también permitiría que mi código REST se usara y devolviera directamente:

Response(code, Body(contentType, content)) 

que es e asier para construir una respuesta dinámicamente.

así que ...

puedo conseguirlo para compilar (con advertencias de desaprobación) a través de:

case class OK(override val body: Body) extends Response(200, body) 

Sin embargo, esto no parece trabajar con la coincidencia de patrones.

Response(200, Body("application/json", "")) match { 
    case OK(_) => ":-)" 
    case _ => ":-(" 
} 
res0: java.lang.String = :-(

¿Alguna idea sobre cómo podría funcionar esto? Estoy abierto a diferentes enfoques, pero este fue mi intento de encontrar un uso práctico para las clases de casos

Respuesta

10

Existen varias razones por las que las clases de casos shouldn't be subclassed. En su caso, el problema es que OK es otro tipo que (un subtipo de) Response, por lo tanto, la coincidencia falla (incluso si los argumentos coinciden, el tipo no coincide).

En su lugar, querrá custom extractors. Por ejemplo:

case class Response(code: Int, body: String) 
object OK { 
    def apply(body: String) = Response(200, body) 
    def unapply(m: Response): Option[String] = m match { 
    case Response(200, body) => Some(body) 
    case _     => None 
    } 
} 

def test(m: Response): String = m match { 
    case OK(_) => ":-)" 
    case _  => ":-(" 
} 

test(Response(300, "Hallo")) // :-(
test(Response(200, "Welt")) // :-) 
test(OK("Welt"))    // :-) 

Hay pocos más ejemplos de extractores personalizados en this thread.

+0

Ah, gracias - Veo que me he perdido por completo el propósito de cancelar la aplicación hasta que esto; esto es muy útil. Lo probaré completamente con mi código para asegurarme de haberlo cubierto y lo aceptaré más tarde esta noche. – 7zark7

+0

Buena respuesta @Sciss. Los extractores personalizados son una de las cosas que me gustan mucho de Scala. – mergeconflict

+0

@ 7zark7 Tenga en cuenta que cuando utiliza extractores personalizados, pierde la exhaustividad de las clases selladas. –

1

¿Ha mirado la biblioteca scala sin filtrar? http://unfiltered.lessis.me/ Te puede ayudar a resolver tu problema. HTH

+0

Eché un vistazo pero lo dejé porque había demasiadas diapositivas, con 1 oración/a-pocas palabras en cada una. ¿Hay quizás alguna versión de página única que aclare de qué se trata Unfiltered? – KajMagnus

+0

Puede ser que este ayude mejor: https://github.com/softprops/Unfiltered – AndreasScheinert

1

Si bien los extractores personalizados mencionados por 0__ ciertamente se pueden utilizar, perderá garantías exhaustivas de jerarquías de tipo selladas. Si bien en el ejemplo que proporcionó en la pregunta no hay nada sealed, el problema es adecuado para ellos.

En ese caso, mi sugerencia es simplemente asegurarse de que case class esté siempre en la parte inferior de la jerarquía de tipos, y hacer que las clases superiores sean normales. Por ejemplo:

sealed class Response(val statusCode: Int, val body: Body) sealed 
case class Ok(override val body: Body) extends Response(200, body) 
sealed class NotOk(statusCode: Int, body: Body) extends Response(statusCode, body) 
case object NotFound extends NotOk(404, "Not found") 
// and so on... 
+0

Gracias Daniel, aunque mi primera impresión fue que esto no funcionaría si también quisiera permitir coincidencias en Response - Veo que esto puede funcionar si defino no aplicar en un objeto Response como menciona Sciss, y tener los "helpers" como clases de casos. Intentaremos ambos enfoques hoy y veremos qué funciona/funciona mejor aquí. – 7zark7

+0

¿Querías escribir 'response class sealed'? –

+0

@Sciss Sí, y 'NotOk' también. Gracias por señalar mi error. –

Cuestiones relacionadas