連鎖部分関数を使用して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)
}
それでもそれらを連鎖させることはできますか?タプルをそれらにマップできるはずのようですが、その方法がわかりません。