昨天我們談到 Ktor 可以用 Functional Programming 的方式來拆分邏輯,並拆分了一個 route 到其他檔案。
今天我們來繼續看看怎麼做拆分。
Functional Programming 的一個特色,是 function 是可以作為參數隨意傳遞的。
我們以前面的一段程式舉例:
get("/") {
val a = async { client.get<String>("http://127.0.0.1:8080/a") }
val b = async { client.get<String>("http://127.0.0.1:8080/b") }
val c = async { client.get<String>("http://127.0.0.1:8080/c") }
val result = a.await() + b.await() + c.await()
client.close()
call.respondText(result, contentType = ContentType.Text.Plain)
}
其中的 val result = a.await() + b.await() + c.await()
,如果我們想要更換合併的方式而不更改 get("/")
裡面的內容,那我們可以把這段抽成函式:
suspend fun combineResult(a: Deferred<String>, b: Deferred<String>, c: Deferred<String>) =
a.await() + b.await() + c.await()
private suspend fun PipelineContext<Unit, ApplicationCall>.root(
client: HttpClient, combineStrategy: suspend (Deferred<String>, Deferred<String>, Deferred<String>) -> String
) {
val a = async { client.get<String>("http://127.0.0.1:8080/a") }
val b = async { client.get<String>("http://127.0.0.1:8080/b") }
val c = async { client.get<String>("http://127.0.0.1:8080/c") }
val result = combineStrategy(a, b, c)
client.close()
call.respondText(result, contentType = ContentType.Text.Plain)
}
get("/") {
root(client, ::combineResult)
}
這樣,當我想替換邏輯時,我只要寫新的 combineStrategy
就好:
suspend fun combineResult(a: Deferred<String>, b: Deferred<String>, c: Deferred<String>) =
a.await() + b.await() + c.await()
suspend fun combineNewResult(a: Deferred<String>, b: Deferred<String>, c: Deferred<String>) =
b.await() + c.await() + a.await()
private suspend fun PipelineContext<Unit, ApplicationCall>.root(
client: HttpClient, combineStrategy: suspend (Deferred<String>, Deferred<String>, Deferred<String>) -> String
) {
val a = async { client.get<String>("http://127.0.0.1:8080/a") }
val b = async { client.get<String>("http://127.0.0.1:8080/b") }
val c = async { client.get<String>("http://127.0.0.1:8080/c") }
val result = combineStrategy(a, b, c)
client.close()
call.respondText(result, contentType = ContentType.Text.Plain)
}
get("/") {
root(client, ::combineNewResult)
}
root()
裡面的邏輯不需要改動。
有的讀者可能有點印象:這跟物件導向裡面的 Strategy Pattern 不是解決了一樣的問題嗎?沒有錯!這裡正是利用了 Function as first class citizen 的特點,在沒有實作 Strategy Pattern 的情況下,就解決了這個問題。