運用泛型實現 Fluent interface Setter
Fluent Interface(FI),又名流式接口,是一種在程式編寫的方式,這篇文章將描述 FI 的意義與如何在 Swift 5.1 實作。
什麼是 Fluent interface
流式接口可以將指令式語言轉化成宣告式語言,使簡易的邏輯判斷消失,進而加強程式碼的可讀性。在 FI 中有許多應用,我分成了 3 種類型 Transformer
, Getter
, Setter
。
Transformer
我們在操作集合類型的時候,常常使用 filter、map、reduce,像是:
let input = [1, 2, 3]
let output = input.map(String.init)
相比沒有使用 map
函式,我們可以使用 for in 迴圈來處理:
let input = [1, 2, 3]
var output = [String]()
for value in input {
output.append(String(value))
}
在 map 函式中,可以看到原先的 input 是一個 let
的變數,明顯可以看到 output 的生成不會改變來源的數值,因此他是一種 Transform
的功能。
Getter
在一些常見的程式語言中常有空的概念(Null),尤其在 Swift 中更是個不能忽略的事情。當我們要使用一個巢狀型別如下:
struct Person {
var name:String
var pet:Pet?
struct Pet {
var petName:String
var speakSound: String?
}
}
我們有一個 Person 型別,這個 Person 有必須的 name 屬性,和一個非必須的 pet 屬性(Pet型別),而這個 Pet 有必須的 petName,和一個非必須的 speakSound。假如我們需要取得所有 person 的 pet 的speakSound,為了避免空的問題,我們可以用 if else 來處理:
// [進階]:為了避免誤解,我省略了 Implicitly Unwrapped Optional
let theMan = Person(name: "Yu",
pet: Pet(petName: "dog", speakSound: "bark"))
if theMan.pet != nil {
if theMan.pet.speakSound != {
print(theMan.pet.speakSound) // bark
}
}
而在 Swift 中,我們可以使用 Optional chaining 來將這個判斷封裝起來:
let theWoman = Person(name: "Swift",
pet: tir)
print(theWoman.pet?.speakSound ?? "...") // ...
在這個例子,我們看到使用 Optional chaining 可以大幅簡化程式碼的複雜情況,讓取得物件 property 的程式可以不需要明顯的邏輯判斷,而是將判斷封裝至 ?
這個運算符號。
Setter
在物件導向設計模式中,有一個 Builder 模式(生成器模式),它可以將複雜對象的建造過程抽象出來(抽象類別),使這個抽象過程的不同實現方法可以構造出不同表現(屬性)的對象。 圖片來自 https://en.wikipedia.org/wiki/Builder_pattern
在 Swift Foundation 中,DateComponents 是一個常見的 Builder:
var dateComponents = DateComponents()
dateComponents.calendar = .current
dateComponents.year = 2020
dateComponents.month = 3
dateComponents.day = 14
dateComponents.hour = 1
dateComponents.minute = 20
dateComponents.second = 59
dateComponents.date // 2020-03-13 17:20:59 +0000
而在我的 FluentInterface 這個 Swift Package 中,我們可以改變這個逐步賦值的方式:
let dateComponents = DateComponents()+
.calendar(.current)
.year(2020)
.month(3)
.day(14)
.hour(1)
.minute(20)
.second(59)
.unwrappingSubject()
.date
dateComponents.date // 2020-03-13 17:20:59 +0000
是不是相對的更直覺一些呢!
FluentInterface 的不好地方
在 2019 年 appcode.com.tw 的 https://www.appcoda.com.tw/fluent-interface/ 文章中,我受到不少啟發,特別分享這篇好文章。 2020 年 3 月時我在 CocoaHeads Taipei 分享了這個 repo,感謝其他與會的開發者提問有關於效能的問題,是的 FI 效率很不好,在連續 1000000 的 Object 生成並 賦予 60 個不同 property 相同數值時,若使用匿名函式(Anonymous Function),單元測試顯示 10 次執行平均時間約為 0.810 秒
; 而 FluentInterface 實測同樣條件需要 15.796
秒。因此在使用 FI 的時候,考慮效能在大數量的狀況是必須注意的。
線上的 CocoaHeads Taipei 聚會
文末特別感謝許立恆、陳涵宇等等開發者。