2012-06-25 15 views
6

Sto tentando di modellare le risposte dalle API REST come classi di casi su cui è possibile utilizzare la corrispondenza del modello.Modellazione con classe caso Scala

Ho pensato che sarebbe stata una buona idea assumendo l'ereditarietà, ma vedo che questo è deprecato. So che ci sono già domande relative a case case ed ereditarietà, ma la mia domanda è più su come modellerai il seguente "giusto modo" qui senza ereditarietà.

ho iniziato con le seguenti due classi case, che funzionano bene:

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

cioè una chiamata REST sarebbe tornato con qualcosa di simile:

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

che potevo modello partita come:

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") 
} 

ecc. Che funziona bene.

Dove mi sono imbattuto nei guai è: Vorrei estensioni di supporto per queste classi di casi, come ad esempio:

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) 

in modo che io possa fare semplificato modello corrisponde in questo modo:

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") 
} 

e inoltre che consentirebbe anche al mio codice REST di utilizzare e restituire direttamente:

Response(code, Body(contentType, content)) 

che è e asier per costruire una risposta in modo dinamico.

così ...

posso farlo per compilare (con avvisi deprecazione) tramite:

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

Tuttavia, questo non sembra funzionare con pattern matching.

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

Qualche idea su come potrebbe funzionare? Sono aperto a diversi approcci, ma questo è stato il mio tentativo di trovare un utilizzo pratico per le classi di casi

risposta

10

Ci sono diversi motivi per cui le classi di casi shouldn't be subclassed. Nel tuo caso, il problema diventa che OK è un altro tipo di (un sottotipo di) Response, quindi la corrispondenza non riesce (anche se gli argomenti corrispondono, il tipo non corrisponde).

Si desidera invece custom extractors. Per esempio:

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"))    // :-) 

ci sono pochi altri esempi di estrattori personalizzati in this thread.

+0

Ah, grazie - Vedo che mi sono totalmente perso lo scopo di inapplicare fino a questo; questo è molto utile. Verificherò tutto questo con il mio codice per assicurarmi di aver coperto e accetterò più tardi stasera. – 7zark7

+0

Buona risposta @Sciss. Gli estrattori personalizzati sono una delle cose che mi piace molto di Scala. – mergeconflict

+0

@ 7zark7 Si noti che quando si utilizzano estrattori personalizzati si perdono garanzie esaustive di classi sigillate. –

1

Hai guardato la libreria di scala non filtrata? http://unfiltered.lessis.me/ Potrebbe aiutarti ad avvicinarti al tuo problema. HTH

+0

Ho dato un'occhiata ma ho smesso perché c'erano troppe diapositive, con 1 frase/a-poche parole su ciascuna. Esiste forse una versione a pagina singola che chiarisca cosa sia Unfiltered? – KajMagnus

+0

Forse questo aiuta meglio: https://github.com/softprops/Unfiltered – AndreasScheinert

1

Mentre gli estrattori personalizzati menzionati da 0__ possono certamente essere utilizzati, si perderanno le garanzie esaustive delle gerarchie di tipo sealed. Mentre nell'esempio che hai fornito nella domanda non c'è niente, sealed, il problema è adatto a loro.

In questo caso, il mio suggerimento è quello di verificare semplicemente che si trovi sempre nella parte inferiore della gerarchia di tipi e rendere normali le classi superiori. Ad esempio:

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

Grazie Daniel, mentre la mia prima impressione era che non avrebbe funzionato se volevo anche consentire le corrispondenze su Response - Vedo che questo potrebbe funzionare se definisco non applicabile su un oggetto Risposta come menziona Sciss e gli "helper" sono le classi dei casi. Proveremo entrambi gli approcci oggi e vedremo cosa si adatta/funziona meglio qui. – 7zark7

+0

Intendevi scrivere "Risposta di classe sigillata"? –

+0

@Sciss Sì e anche 'NotOk'. Grazie per aver segnalato il mio errore. –

Problemi correlati