在介紹泛型之前,先來講解一下 OOP 三大特性中的 多型
。
多型是一種操作介面,可以使用同一種方法操作不同的型別資料,透過多型可以提升程式碼的維護性,但是光是多型就有很多種類,廣費的定義就是分為動態多型與靜態多型,至於我們今天要聊到的就是屬於靜態多型的一種:參數多型 ( parametric polymorphism ),就是所謂的泛型。
我們先來看一個例子,假使三種忍者:感知忍者
、醫療忍者
還有影級忍者
:
enum 感知忍者 {
case 長門
case 二代土影
case 扉間
}
enum 醫療忍者 {
case 香燐
case 小櫻
case 綱手
}
enum 影級忍者 {
case 水影
case 風影
case 火影
case 土影
case 雷影
}
然而感知忍者需要出任務,所以定義一個方法叫做感知忍者出任務
:
func 感知忍者出任務(_ ninja: 感知忍者) {
print("\(ninja) 出任務!")
}
感知忍者出任務(.長門) // 長門 出任務
但是如果今天另外兩種忍者也要出任務,醫療忍者
和影級忍者
,這時候可能就要再新增兩種方法,所以到最後就會有三種方法:
func 感知忍者出任務(_ 感知忍者: 感知忍者) {
print("\(感知忍者) 出任務!")
}
func 醫療忍者出任務(_ 醫療忍者: 醫療忍者) {
print("\(醫療忍者) 出任務!")
}
func 影級忍者出任務(_ 影級忍者: 影級忍者) {
print("\(影級忍者) 出任務!")
}
感知忍者出任務(.長門) // 長門 出任務!
醫療忍者出任務(.香燐) // 香燐 出任務!
影級忍者出任務(.火影) // 火影 出任務!
這樣每增加一種類型的忍者就要多寫一個方法,到最後就會沒完沒了,而且實際要做的內容都是一樣的,所以最好的方式就是希望能透過同一種方法,針對不同型別的資料做相同的處理,這種概念就是 Gerneric Programming。
Generic Programming 就是把原本在 Design Time 應該要決定的參數型別,挪到 Compile Time 再來分析判斷,但是這樣講還是有點模糊對吧,我們直接來看例子,把上面的範例改寫一下:
func 忍者出任務<忍者>(_ 忍者: 忍者) {
print("\(忍者) 出任務!")
}
忍者出任務(感知忍者.長門)
忍者出任務(醫療忍者.香燐)
忍者出任務(影級忍者.火影)
透過 忍者出任務(_ 忍者)
針對不同型別的參數,進行相同操作,這樣就把原本三個不同的方法,統一成一種了,但是,這是什麼語法?
func 方法名稱<T>(參數: T) {
...
}
這就是具有 Generic 的 Parameter,在 Design Time 時,透過<>
來定義泛型參數,然而 T
是一個自定義的參數名稱,Swift 創造一個 Type 給我們作為暫時性的型別,然而這個型別可以接受外部傳入的任何型別的資料,等到編譯的時候就會依照會不傳入的資料型別,把 T
全部轉換為該型別。
所以回來看上面的範例,我們透過 <忍者>
作為一個 Generic Type,並且定義一個參數 忍者
並標記 Generic Type,所以在呼叫的時候,同樣是呼叫 忍者出任務
,但是因為傳入的參數分別是不同的型別,他就會依照這個型別來去做轉換,把 忍者
這個 Generic Type 替換掉。
假使今天有個 isEqual()
用來比較兩數是否相等,我們也許可以這樣寫:
func isEqual<Data>(x: Data, y: Data) -> Bool {
x == y
}
看似沒什麼問題,但是錯誤隨之而來的出現:Binary operator '==' cannot be applied to two 'Data' operands
,這段錯誤就是跟你說你不能用 ==
來比較兩個 Data
型別的資料,那為什麼會有這個錯誤?
一般我們使用的基本型別,像是 Int、Float 或是 Double,其實都遵循了 Equatable
協定,然而如果我們要使用 ==
運算子來比較數值,資料型別就必須遵循 Equatable
協定,所以我們可以在 Generic Type 後方來遵循 Protocol,來進行型別約束:
func isEqual<Data: Equatable>(_ x: Data, _ y: Data) -> Bool {
x == y
}
isEqual(10, 10) // true
isEqual("老虎", "老鼠") // false