Swift 程式語言 — Generics(2)
讓我們來看看什麼是 Associated Types 吧。
前言:
如果還沒有看過泛型相關概念的讀者,可以參考我的上一篇文章:
|Associated Types
當定義協議時,定義一個或多個關聯類型(Associated types)作為協議定義的一部分有時很有用。關聯類型給協議中使用到的類型一個佔位符名稱。在協議被採用之前,沒有被指定用於關聯類型的實際類型。關聯類型使用 associatedtype
關鍵字來指定的。
|關聯類型的操作
這是一個名為 Container
的協議範例,該協議宣告了一個叫做 Item
的關聯類型:
Container
協議定義了任何容器必須提供的三個功能:
- 它必須可以使用
append(_:)
方法來添加新項目到容器中。 - 它必須可以透過返回
Int
值的count
屬性來訪問容器中項目的數量。 - 它必須可以使用帶有
Int
的索引值的下標來檢索每個容器中的項目。
這個協議沒有指定容器中的物品應該被如何儲存或它們允許使用的類型。該協議只有指定任何類型必須提供的三個功能點才能被視為一個 Container
。只要滿足這三個要求,遵循的類型就可以提供其他功能。
遵循 Container
協議的任何類型都必須能夠指定其存儲的值的類型。具體來說,它必須確保只有正確類型的項目能被添加到容器中,並且必須清楚其下標返回的類型。
為了定義這些要求,Container
協議需要一個方法來引用容器中將要容納的元素,而無需知道特定容器的類型。容器協議需要指定傳遞給 append(_:)
方法的任何值必須與容器元素類型具有相同的類型,並且容器的下標返回的值必須與容器的元素類型具有相同的類型。
為此,Container
協議定義了一個稱為 Item
關聯類型,寫為 associatedtype Item
。該協議沒有定義 Item
是什麼,該訊息留給任何遵循類型來提供。儘管如此,Item
別名還提供一個方式來引用 Container
中的項目類型,並且定義一個與 append(_:)
方法和下標一起使用的類型,來確保任何 Container
的預期行為是被強制的。
這是一個非泛型版本的 IntStack
,適用於遵循 Container
協議:
IntStack
類型實現了 Container
協議的所有三個要求,並且在每個情況下都包裝了 Intstack
類型的現有功能的一部分來滿足這些要求。
此外,IntStack
為了 Container
的這個實現,指定要使用的合適 Item
為 Int
類型。typealias Item = Int
的定義將 Item
的抽象類型轉換為具體的 Int
類型。
由於 Swift 的類型推斷,你不需要實際的宣告一個 Int
的 Item
作為 IntStack
定義的一部分。因為 IntStack
遵循了所有 Container
的所有要求,因此 Swift 只需查看 append(_:)
方法的 item
參數的類型與下標的返回類型,即可推斷合適的 Item
使用。確實,如果從上面的程式碼刪除了 typealias Item = Int
這行,那麼一切仍然有效,因為很明顯 Item
應該使用哪種類型。
你還可以使用泛型 Stack
類型符合 Container
協議:
這次,類型參數 Element
被作為 append(_:)
方法的 item
參數的類型以及下標的返回類型。因此,Swift 可以推斷 Element
是用作此特定容器的 Item
的合適類型。
|擴展現有類型來指定關聯值
你可以擴展現有類型來添加對協議的一致性,如 Adding Protocol Conformance with an Extension 中所述。這包括具有關聯類型的協議。
Swift 中的 Array
類型已經提供了 append(_:)
方法,count
屬性以及帶有 Int
索引的下標來檢索其元素。這三個功能皆符合 Container
協議的要求。這意味著你只需要宣告 Array
採用協議即可擴展 Array
來使其遵循 Container
協議。你可以使用一個空的擴展來達成此操作,如 Declaring Protocol Adoption with an Extension 中所述:
Array
現有的 append(_:)
方法和下標,Swift 可以推斷要使用於 Item
的合適類型,如同上面的泛型 Stack
類型一樣。定義此擴展後,可以將任何 Array
作為 Cotainer
。
|添加約束到關聯類型
你可以類型添加約束到協議中的關聯類型,來要求符合類型的類型滿足這些約束。例如,以下的程式碼定義了 Container
的版本,該版本要求容器中的 item
是相等的(equatable)。
為了遵循此版本的 Container
,容器 Item
類型必須符合 Equatable
協議。
|在關聯類型的約束中使用協議
協議可以作為其自身要求的一部分出現。例如,這裡是一個優化 Container
協議的協議,添加了 suffix(_:)
方法的要求。suffix(_:)
方法從容器的最後返回給定數量的元素,並將他們存儲在 Suffix
類型的實例中。
在這個協議中,Suffix
是關聯類型,像是上面 Container
範例中的 item 類型。Suffix
具有兩個約束:其必須符合 SuffixableContainer
協議(目前正在定義的協議),並且 Item
類型必須與容器的 Item
類型相同。對 Item
的約束是通用的 where
子句,會在後面的 “具有泛型的 where 子句的關聯類型” 中說明。
這是上面的泛型類型的 Stack
類型的擴展,它增加了對 SuffixableContainer
協議的一致性:
在上面的範例中,Stack
的 Suffix
關聯類型也是 Stack
,因此 Stack
上的後綴操作返回另一個 Stack
。另外,符合 SuffixableContainer
的類型可以具有與自身不同的 Suffix
類型,這意味著後綴操作可以返回不同的類型。舉例,這是個對非泛型 IntStack
類型的擴展,其添加了 SuffixableContainer
一致性,使用 Stack<Int>
作為其後綴類型而不是 IntStack
: