2012-06-25 13 views
6

Próbuję modelować odpowiedzi z interfejsów REST API jako klasy przypadków, w których mogę używać dopasowywania wzorców.Modelowanie z klasą obudowy Scala

Pomyślałem, że dobrze byłoby założyć dziedziczenie, ale widzę, że jest to przestarzałe. Wiem, że istnieją już pytania dotyczące klas przypadków i dziedziczenia, ale moje pytanie jest bardziej o tym, jak można modelować następujące "właściwą drogę" tutaj bez dziedziczenia.

zacząłem z dwóch następujących klasach przypadków, które działają dobrze:

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

czyli rozmowy reszta będzie powrócić z czymś takim:

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

które mogłem wzór mecz jak:

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

itp. Co działa dobrze.

Gdzie wpadłem w kłopoty to: Chciałbym rozszerzenia pomocnicze dla tych klas sprawy, takich jak:

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) 

tak, że mogę nie uproszczony wzór pasuje tak:

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

a także, który umożliwiłby również mój kod REST do bezpośredniego użycia i zwrotu:

Response(code, Body(contentType, content)) 

która jest e aby dynamicznie budować odpowiedź.

tak ...

mogę go skompilować (z ostrzeżeniami amortyzację) poprzez:

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

Jednak to nie wydają się działać z dopasowywania wzorca.

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

Wszelkie pomysły na to, jak to może działać? Jestem otwarty na różne podejścia, ale to była moja próba znalezienia praktycznego zastosowania dla klas przypadków

Odpowiedz

10

Istnieje kilka powodów, dla których klasy przypadków shouldn't be subclassed. W twoim przypadku problem polega na tym, że OK jest innym typem niż (podtyp) Response, dlatego dopasowanie nie powiedzie się (nawet jeśli argumenty są zgodne, typ nie pasuje).

Zamiast tego będziesz potrzebować custom extractors. Na przykład:

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

Istnieje kilka dodatkowych przykładów niestandardowych ekstraktorów w this thread.

+0

Ach, dziękuję - widzę, że całkowicie straciłem cel, by nie stosować się do tego; To jest bardzo pomocne. Przetestuję to w pełni z moim kodem, aby upewnić się, że omówiłem i zaakceptuję później wieczorem. – 7zark7

+0

Dobra odpowiedź @ Siss. Własne ekstraktory to jedna z rzeczy, które bardzo lubię w Scali. – mergeconflict

+0

@ 7zark7 Zwróć uwagę, że podczas korzystania z niestandardowych ekstraktorów tracisz gwarancje szczelności zamkniętych klas. –

1

Czy obejrzałeś bibliotekę scala niefiltrowaną? http://unfiltered.lessis.me/ Może to pomóc w rozwiązaniu problemu. HTH

+0

Spojrzałem, ale przestałem, ponieważ było zbyt wiele slajdów, z jednym zdaniem/kilkoma słowami na każdym. Czy jest jakaś jedna wersja strony, która wyjaśnia, czym jest Unfiltered? – KajMagnus

+0

To może pomóc lepiej: https://github.com/softprops/Unfiltered – AndreasScheinert

1

Podczas gdy niestandardowe ekstraktory wymienione przez 0__ mogą być z pewnością używane, utracisz gwarancję szczelności zamkniętych hierarchii typów. Podczas gdy w przykładzie podanym w pytaniu nie ma nic, to problem jest dla nich dobrze dopasowany.

W takim przypadku, moja sugestia polega na upewnieniu się, że case class jest zawsze na dole hierarchii typów i sprawiają, że wyższe klasy są normalne. Na przykład:

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

Dziękuję Danielowi, podczas gdy moje pierwsze wrażenie było takie, że nie zadziałałoby, gdybym również chciał dopuścić dopasowania w odpowiedzi - widzę, że to może zadziałać, jeśli zdefiniuję unapply na obiekcie Response, o czym wspomina Sciss, a "pomocnikami" są klasy case. Wypróbuję oba podejścia dzisiaj i zobaczę, co pasuje/działa najlepiej tutaj. – 7zark7

+0

Czy chciałeś napisać 'zapieczętowana odpowiedź klasy'? –

+0

@Sciss Tak, oraz 'NotOk' również. Dzięki za wskazanie mojego błędu. –