2012-06-25 137 views
6

我试图将来自REST API的响应建模为案例类,我可以使用模式匹配。斯卡拉案例类建模

我认为这将是一个很好的假设继承,但我发现这已被弃用。 我知道已经有关于案例类和继承的问题,但是我的问题更多地是关于如何在这里继承而不是继承。

我开始用下面的两个案例类,做工精细:

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

即一个REST调用将喜欢的东西返回:

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

,我可以的模式匹配,如:

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

等工作正常。

我在哪里遇到了麻烦是:我想这些情况下类的辅助扩展,如:

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) 

,这样我可以做简单的图案像这样的比赛:

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

也可以让我的REST代码直接使用并返回:

Response(code, Body(contentType, content)) 

这是e asier动态地构建响应。

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

然而,这似乎并没有与模式匹配的工作:

通过

所以......

我可以得到它来编译(用废弃警告)。

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

有关如何工作的任何想法?我接受不同的方法,但这是我尝试寻找案例类别的实际用途

回答

10

为什么案例类别shouldn't be subclassed有几个原因。在你的情况下,问题变成OK是另一种类型(Response的子类型),因此匹配失败(即使参数匹配,类型不匹配)。

改为您需要custom extractors。例如:

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

this thread中有几个自定义提取器的例子。

+0

啊,谢谢 - 我看到我完全错过了直到此为止的无用的目的;这非常有帮助。将我的代码完全测试出来,以确保我已经覆盖并且将在今晚晚些时候接受。 – 7zark7

+0

好答案@Sciss。自定义提取器是我对Scala很喜欢的一件事。 – mergeconflict

+0

@ 7zark7请注意,当您使用自定义提取器时,您将失去密封类的详尽性保证。 –

1

你看过未经过滤的scala库吗? http://unfiltered.lessis.me/ 它可能会帮助你接近你的问题。 HTH

+0

我看了一看,但我放弃了,因为幻灯片太多,每句一个/几个字。是否有任何单页版本可以阐明Unfiltered的全部内容? – KajMagnus

+0

可能是这一个更好的帮助:https://github.com/softprops/Unfiltered – AndreasScheinert

1

尽管0__提到的自定义提取器当然可以使用,但您将失去密封类型层次结构的全面性保证。虽然在问题中给出的例子中没有任何sealed,但问题很适合他们。

在这种情况下,我的建议是简单地确保case class始终位于类型层次结构的底部,并使上层类正常。例如:

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

感谢丹尼尔,虽然我的第一印象是,如果我还想让响应匹配响应,这是行不通的 - 我看到这可能工作,如果我定义在Sciss提到的Response对象上不应用,并且将“助手”作为案例类。今天会尝试两种方法,看看最适合/最适合这里。 – 7zark7

+0

你的意思是写'密封类响应'吗? –

+0

@Sciss是,还有'NotOk'。感谢您指出我的错误。 –