2

我有一个使用链式部分函数处理 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 或 Other。我想要接受不同类型的参数的部分函数(它们仍然可以返回相同的类型)。但我不知道如何组合它们。

对于一个具体的问题 - 假设示例验证器方法更改为如下所示:

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

我还能把它们连在一起吗?似乎我应该能够将元组映射到它们,但我不知道如何。

4

3 回答 3

6

我非常喜欢将 scalaz 的 Validation 用于此类事情。它使您可以相当多地控制要对错误做什么以及如何处理它们。这是使用您的控制器的示例:

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 理解来组合它们。

于 2013-01-17T18:46:49.620 回答
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 验证。)

于 2013-01-18T22:35:10.547 回答
0

您可以使 PartialFunction 更通用,使其 PartialFunction[Any, (String, Int)] Altho, mb 它会更慢。不知道 PartialFunction 下的匹配机制

于 2014-07-29T11:14:46.520 回答