2013-01-17 35 views
2

我有一個模式來處理使用鏈接部分函數的Web服務請求(我認爲這是一個責任鏈模式鏈)。在我的例子中,假設請求有兩個參數,一個字符串Id和一個日期。有一個涉及id的驗證步驟,檢查日期的驗證步驟,以及最終使用兩者的一些業務邏輯。所以我讓他們實現像這樣:scala:我如何鏈接不同類型的部分函數

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,但沒有日期參數,並且可能它有一些需要驗證的第三種類型的新參數。我不想繼續擴展那個元組到(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) 
} 

,我還可以把它們連在一起?我似乎應該能夠將元組映射到它們,但我無法弄清楚如何。

回答

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

您可以將不同的驗證功能關閉到自己的世界,然後它們組合,只要你願意,在斯卡拉的理解。

+0

謝謝諾亞。我還沒有使用scalaz,但驗證器看起來相當不錯。理解風格運作良好。我會檢查出來的! – ryryguy

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

這種方法是有用的,但我仍然懷疑是否沒有更直接的方式來完成它。 (在我有機會玩了一下後,我也可以切換到諾亞建議的斯卡拉茲驗證。)

0

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

相關問題