2

連鎖部分関数を使用してWebサービス要求を処理するパターンがあります(これは一連の責任パターンだと思いますか?)。私の例では、リクエストに2つのパラメータ、文字列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を取得したが、日付パラメーターがなく、検証が必要な3番目のタイプの新しいパラメーターがあるとします。そのタプルを拡張し続けたくないので(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の検証を使用するのが大好きです。これにより、エラーを処理する方法とエラーの処理方法をかなり制御できます。これは、コントローラーを使用した例です。

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で理解するためにいつでもそれらを作成できます。

于 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)]にすることができます。また、遅くなります。PartialFunctionの下で一致するメカニズムがわからない

于 2014-07-29T11:14:46.520 に答える