我有一个使用链式部分函数处理Web服务请求的模式(我认为这是一个责任链模式?)。在我的例子中,假设请求有两个参数,一个字符串Id和一个日期。有一个涉及id的验证步骤,一个检查日期的验证步骤,以及最后使用两者的一些业务逻辑。所以我实现了它们:
object Controller {
val OK = 200
val BAD_REQUEST = 400
type ResponseGenerator = PartialFunction[(String, DateTime), (String, Int)]
val errorIfInvalidId:ResponseGenerator = {
case (id, _) if (id == "invalid") => ("Error, Invalid ID!", BAD_REQUEST)
}
val errorIfFutureDate:ResponseGenerator = {
case (_, date) if (date.isAfter(DateTime.now)) => ("Error, date in future!", BAD_REQUEST)
}
val businessLogic:ResponseGenerator = {
case (id, date) => {
// ... do stuff
("Success!", OK)
}
}
def handleRequest(id:String, date:DateTime) = {
val chained = errorIfInvalidId orElse errorIfFutureDate orElse businessLogic
val result: (String, Int) = chained(id, date)
// make some sort of a response out of the message and status code
// e.g. in the Play framework...
Status(result._2)(result._1)
}
}
我喜欢这种模式,因为它非常具有表现力 - 通过查看链接函数,您可以轻松掌握控制器方法逻辑。而且,我可以轻松地混合和匹配不同请求的不同验证步骤。
问题在于,当我尝试扩展此模式时,它开始崩溃。假设我的下一个控制器需要一个我要验证的id,但没有date参数,也许它有一些需要验证的第三种类型的新参数。我不想继续将该元组扩展到(String, DateTime, Other)
并且必须传入虚拟DateTime或其他。我想要部分函数接受不同类型的参数(它们仍然可以返回相同的类型)。但我无法弄清楚如何撰写它们。
对于具体问题 - 假设示例验证器方法更改为如下所示:
val errorIfInvalidId:PartialFunction[String, (String, Int)] = {
case id if (id == "invalid") => ("Error, Invalid ID!", BAD_REQUEST)
}
val errorIfInvalidDate:PartialFunction[DateTime, (String, Int)] = {
case date if (date.isAfter(DateTime.now)) => ("Error, date in future!", BAD_REQUEST)
}
我还可以将它们连在一起吗?看起来我应该能够将元组映射到它们,但我无法弄清楚如何。
答案 0 :(得分:6)
我很喜欢使用scalaz验证这样的事情。它为您提供了很多控制权,可以控制错误以及如何处理错误。这是一个使用你的控制器的例子:
import scalaz._
import Scalaz._
object Controller {
val OK = 200
val BAD_REQUEST = 400
case class Response(response: String, status: Int)
def validateIfInvalidId(id: String) = (id == "invalid") ?
Response("Error, Invalid ID!", BAD_REQUEST).fail[String] |
id.success[Response]
def validateIfFutureDate(date: DateTime, currentDate: DateTime = DateTime.now) = (date.isAfter(currentDate)) ?
Response("Error, date in future!", BAD_REQUEST).fail[DateTime] |
date.success[Response]
def handleRequest(id: String, date: DateTime) = {
val response = for {
validatedId <- validateIfInvalidId(id)
validatedDate <- validateIfFutureDate(date)
} yield {
// ... do stuff
Response("Success!", OK)
}
// make some sort of a response out of the message and status code
// e.g. in the Play framework...
response.fold(
failure => Status(failure.response, failure.status),
success => Status(success.response, success.status)
)
}
}
您可以将不同的验证功能移到他们自己的世界中,然后随时使用scala中的for comprehension组合它们。
答案 1 :(得分:0)
好的,我找到了一种方法,这似乎并不太糟糕。最初我认为将部分函数的“基础”版本包含在另一个采用元组的部分函数中可能会起作用。但我无法弄清楚如何做到这一点,直到我找到了在案件保护声明中使用isDefined
的显而易见的想法。像这样:
// "base" version
val errorIfInvalidId:PartialFunction[String, (String, Int)] = {
case id if (id == "invalid") => ("Error, Invalid ID!", BAD_REQUEST)
}
// wrapped to take tuple as parameter
val wrappedErrorIfInvalidId:PartialFunction[(String, DateTime), (String, Int)] = {
case (id, _) if (errorIfInvalidId.isDefinedAt(id)) => errorIfInvalidId(id)
}
这种方法是可用的,但我仍然想知道是否没有更直接的方法来实现它。 (在我有机会玩一下之后,我也可以切换到Noah建议的Scalaz验证。)
答案 2 :(得分:0)
你可以让PartialFunction更通用,使它成为PartialFunction [Any,(String,Int)] 但是,它会慢一些。不知道PartialFunction下的匹配机制