2012-06-25 24 views
6

Ich versuche, Antworten von REST-APIs als Fallklassen zu modellieren, für die ich die Mustererkennung verwenden kann.Modellierung mit Scala-Fallklasse

Ich dachte, es wäre eine gute Passform unter Annahme der Vererbung, aber ich sehe, dass dies veraltet ist. Ich weiß, es gibt bereits Fragen im Zusammenhang mit Fallklassen und Vererbung, aber meine Frage ist mehr darüber, wie Sie das folgende "den richtigen Weg" hier ohne Vererbung modellieren würden.

ich mit den folgenden zwei Fallklassen gestartet, die gut funktionieren:

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

dh ein REST Anruf zurückkehren würde mit so etwas wie:

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

welche ich konnte Mustererkennung wie:

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

usw. das funktioniert gut.

Wo ich in Schwierigkeiten lief ist: Ich Helfer Erweiterungen für diesen Fall Klassen möchten, wie zum Beispiel:

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) 

so dass ich Muster wie diese Spiele vereinfacht tun können:

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

und die auch erlauben würde, dass mein REST-Code direkt verwendet und zurückgegeben wird:

Response(code, Body(contentType, content)) 

was ist e asier, dynamisch eine Antwort zu erstellen.

so ...

Ich kann es (mit deprecation Warnungen) zu kompilieren über:

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

Allerdings scheint dies nicht mit Mustervergleich zu arbeiten.

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

Irgendwelche Ideen, wie dies funktionieren könnte? Ich bin offen für verschiedene Ansätze, aber das war mein Versuch, eine praktische Verwendung für Fallklassen zu finden

Antwort

10

Es gibt mehrere Gründe, warum Fallklassen shouldn't be subclassed. In Ihrem Fall wird das Problem, dass OK ist ein anderer Typ als (ein Subtyp von) Response, daher die Übereinstimmung fehlschlägt (auch wenn die Argumente übereinstimmen, stimmt der Typ nicht überein).

Sie wollen stattdessen custom extractors. Zum Beispiel:

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

Es gibt einige Beispiele für kundenspezifische Extraktoren in this thread.

+0

Ah, danke - ich sehe, ich habe den Zweck der unapply bis zu diesem völlig verpasst; das ist sehr hilfreich. Ich werde das mit meinem Code austesten, um sicherzugehen, dass ich mich bedeckt habe und später Abend annehmen werde. – 7zark7

+0

Gute Antwort @Sciss. Benutzerdefinierte Extraktoren sind eine der Sachen, die ich sehr an Scala mag. – mergeconflict

+0

@ 7zark7 Beachten Sie, dass Sie bei Verwendung von benutzerdefinierten Extraktoren die Vollständigkeit der garantierten Klassen verlieren. –

1

Haben Sie die Scala-Bibliothek ungefiltert betrachtet? http://unfiltered.lessis.me/ Es kann Ihnen bei der Annäherung an Ihr Problem helfen. HTH

+0

Ich habe es mir angesehen, aber ich habe aufgehört, weil es zu viele Dias gab, mit jeweils 1 Satz/einigen Wörtern. Gibt es vielleicht eine einzelne Seitenversion, die klarstellt, worum es bei Unfiltered geht? – KajMagnus

+0

Vielleicht hilft dieser besser: https://github.com/softprops/Unfiltered – AndreasScheinert

1

Während benutzerdefinierte Extraktoren von 0__ sicherlich verwendet werden können, werden Sie die Vollständigkeitsgarantie von geschlossenen Hierarchien verlieren. Während in dem Beispiel, das Sie in der Frage gaben, gibt es nichts sealed, das Problem ist gut für sie geeignet.

In diesem Fall ist mein Vorschlag, einfach sicherzustellen, dass case class immer am Ende der Typhierarchie ist, und die oberen Klassen normal zu machen. Zum Beispiel:

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

Danke Daniel, während mein erster Eindruck war, dass dies nicht funktionieren würde, wenn ich auch Übereinstimmungen bei Response zulassen wollte - ich sehe, dass das funktionieren kann, wenn ich definiere Anwenden auf ein Response-Objekt, wie Sciss erwähnt, und die "Helfer" müssen die Fall-Klassen sein. Werde beide Ansätze heute ausprobieren und sehen, was am besten passt/funktioniert. – 7zark7

+0

Meinen Sie "versiegelte Antwortklasse" zu schreiben? –

+0

@Sciss Ja, und 'NotOk' auch. Danke, dass du mich auf meinen Fehler aufmerksam gemacht hast. –