原文來(lái)自Raywenderlich键闺,作者Eric Kerber,原文鏈接:
https://www.raywenderlich.com/109156/introducing-protocol-oriented-programming-in-swift-2
Note: 本教程需要Xcode7以及Swift2淑际,可以在Apple官方網(wǎng)站下載(譯者注:原文發(fā)布是Swift2還處于beta階段)畏纲。
在WWDC2015時(shí),Apple發(fā)布了Swift語(yǔ)言的第二個(gè)大的版本更新——Swift 2春缕。新版本包含了很多新的語(yǔ)言特性(譯者注:中文改動(dòng)可以看這里盗胀,不過(guò)推薦閱讀英文版改動(dòng)日志),讓Swift在使用的時(shí)候更加得心應(yīng)手锄贼。
眾多改動(dòng)之中票灰,最引人注意的就是protocol extensions。在Swift第一版中宅荤,我們可以通過(guò)extension來(lái)為已有的class屑迂,struct或enum拓展功能。而在Swift 2中冯键,我們也可以為protocol添加extension惹盼。
可能一開(kāi)始看上去這個(gè)新特性并不起眼,實(shí)際上protocol extensions非常強(qiáng)大惫确,以至于可以改變Swift之前的某些編程思想手报。在本教程中,我們會(huì)展示如何創(chuàng)建并使用protocol extensions改化,以及隨之而來(lái)的一些新技巧和面向接口編程(Protocol-oriented programming掩蛤,以下簡(jiǎn)稱POP)模式對(duì)代碼的影響。
你將會(huì)了解Swift團(tuán)隊(duì)是如何使用protocol extensions來(lái)改善Swift標(biāo)準(zhǔn)庫(kù)陈肛,以及如何使用這些技巧來(lái)改善自己的代碼揍鸟。
準(zhǔn)備工作
首先,創(chuàng)建一個(gè)新的playground句旱。在Xcode中阳藻,選擇File\New\Playground...,然后將playground命名為SwiftProtocols前翎。platform可以選擇任意選項(xiàng)稚配,之后我們要構(gòu)建的代碼都是平臺(tái)無(wú)關(guān)的畅涂。點(diǎn)擊Next選擇文件路徑港华,最后點(diǎn)擊Create。
新的playground打開(kāi)之后午衰,加入如下代碼:
protocol Bird {
var name: String { get }
var canFly: Bool { get }
}
protocol Flyable {
var airspeedVelocity: Double { get }
}
以上代碼定義了一個(gè)簡(jiǎn)單的protocol——Bird立宜。它有name和canFly兩個(gè)屬性。還有一個(gè)protocol叫做Flyable臊岸,定義了屬性airspeedVelocity橙数。
之前,你或許會(huì)把Flyable定義成一個(gè)基類(base class)帅戒,然后利用繼承的方法來(lái)定義Birds以及其它“會(huì)飛”的類型灯帮,例如飛機(jī)(譯者注:Flyable意為飛行物崖技,Birds就是鳥(niǎo))。而從現(xiàn)在開(kāi)始钟哥,所有的東西都開(kāi)始被定義為protocol了迎献!
當(dāng)我們開(kāi)始定義具體的類型的時(shí)候,你就會(huì)發(fā)現(xiàn)腻贰,這一切會(huì)讓整個(gè)系統(tǒng)變得多么靈活吁恍。
定義Protocol-conforming類型
將下面定義struct的代碼添加到playground中:
struct FlappyBird: Bird, Flyable {
let name: String
let flappyAmplitude: Double
let flappyFrequency: Double
let canFly = true
var airspeedVelocity: Double {
return 3 * flappyFrequency * flappyAmplitude
}
}
這里定義了一個(gè)新的struct類型——FlappyBird。它同時(shí)滿足Bird和Flyable兩個(gè)protocol播演。airspeedVelocity作為一個(gè)computed property冀瓦,由flappyFrequency和flappyAmplitude計(jì)算得出。作為“會(huì)飛的鳥(niǎo)”写烤,F(xiàn)lappyBird將canFly設(shè)置為true翼闽。
接下來(lái),把下面定義兩個(gè)struct的代碼加到playground中(譯者注:Penguin是企鵝顶霞,SwiftBird可以理解為速度很快的鳥(niǎo)):
struct Penguin: Bird {
let name: String
let canFly = false
}
struct SwiftBird: Bird, Flyable {
var name: String { return "Swift \(version)" }
let version: Double
let canFly = true
// Swift is FAST!
var airspeedVelocity: Double { return 2000.0 }
}
Penguin與Bird是is-a關(guān)系肄程,但并不能飛。Aha——幸虧沒(méi)有使用繼承的方式选浑,否則所有的Bird都會(huì)被定義為Flyable蓝厌。對(duì)于SwiftBird,自然而然的古徒,它速度很快拓提,飛行速度的數(shù)值也很高。
然而隧膘,或許你已經(jīng)注意到了代态,目前的實(shí)現(xiàn)還是有一些冗余存在。每一種Bird類型必須聲明canFly屬性疹吃,不管它到底會(huì)不會(huì)飛蹦疑。我們的系統(tǒng)中已經(jīng)有了Flyable的概念,卻并沒(méi)能很好的利用起來(lái)萨驶。
用默認(rèn)實(shí)現(xiàn)來(lái)拓展Protocols
利用protocol extensions歉摧,我們可以定義protocol的默認(rèn)行為。添加下面的代碼到playground中:
extension Bird where Self: Flyable {
// Flyable birds can fly!
var canFly: Bool { return true }
}
這段代碼定義了一個(gè)Bird的extension腔呜, 并設(shè)置了canFly屬性的默認(rèn)行為叁温。當(dāng)實(shí)現(xiàn)Bird protocol的對(duì)象同時(shí)實(shí)現(xiàn)了Flyable protocol的時(shí)候,讓canFly的值總是返回true核畴。換句話說(shuō)膝但,“會(huì)飛的鳥(niǎo)”終于不用再顯式的聲明自己“會(huì)飛”了!
Swift 1.2 引入了與if-let關(guān)聯(lián)的where語(yǔ)法谤草,Swift 2則把where進(jìn)一步用于了有條件的拓展protocol跟束。
刪除FlappyBird和SwiftBird定義里面的let canFly = true莺奸。你會(huì)發(fā)現(xiàn)playground依然成功build,因?yàn)閜rotocol extension幫你處理了冗余的部分冀宴。
為什么不使用基類(Base Classes)憾筏?
Protocol extensions和默認(rèn)行為(Default Behavior)可能看起來(lái)和使用基類很相似。更確切一點(diǎn)花鹅,很像其它一些語(yǔ)言中的abstract class氧腰。但在Swift中,protocol extensions主要有如下一些便利:
- 因?yàn)轭愋涂梢酝瑫r(shí)滿足超過(guò)一個(gè)protocol刨肃,我們可以用多個(gè)不同的protocol的默認(rèn)行為來(lái)構(gòu)建這個(gè)類型對(duì)象古拴。不同于某些語(yǔ)言支持的多重繼承(multiple inheritance),protocol extensions并不會(huì)引入額外的狀態(tài)真友。
- Protocols可以用于類黄痪,結(jié)構(gòu)體和枚舉類型(classes,structs and enums)盔然,而基類和繼承只能用于類類型(class types)桅打。
換句話說(shuō),protocol extensions為值類型(value types)提供了定義默認(rèn)行為的方法愈案。
展示了struct類型的例子之后挺尾,讓我們把下面的enum定義加到playground中:
enum UnladenSwallow: Bird, Flyable {
case African
case European
case Unknown
var name: String {
switch self {
case .African:
return "African"
case .European:
return "European"
case .Unknown:
return "What do you mean? African or European?"
}
}
var airspeedVelocity: Double {
switch self {
case .African:
return 10.0
case .European:
return 9.9
case .Unknown:
fatalError("You are thrown from the bridge of death!")
}
}
}
如同其它值類型一樣,你需要做的只是定義正確的屬性來(lái)讓UnladenSwallow滿足兩個(gè)protocol即可(譯者注:UnladenSwallow當(dāng)成是一種燕子就行)站绪。因?yàn)樗瑫r(shí)滿足Bird和Flyable遭铺,所以直接擁有canFly的默認(rèn)實(shí)現(xiàn)。
你難道覺(jué)得這個(gè)用了airspeedVelocity的教程不會(huì)提到Monty Python么恢准?:](譯者注:貌似是作者用了一個(gè)梗魂挂,Monty Python是國(guó)外一個(gè)著名的喜劇團(tuán)體)。
Protocols的擴(kuò)展
大概protocol extensions最常見(jiàn)的用途就是用來(lái)擴(kuò)展外部的protocols了馁筐,往往要么是來(lái)自Swift標(biāo)準(zhǔn)庫(kù)涂召,要么是來(lái)自第三方庫(kù)。
把下面的代碼加入playground:
extension CollectionType {
func skip(skip: Int) -> [Generator.Element] {
guard skip != 0 else { return [] }
var index = self.startIndex
var result: [Generator.Element] = []
var i = 0
repeat {
if i % skip == 0 {
result.append(self[index])
}
index = index.successor()
i++
} while (index != self.endIndex)
return result
}
}
這段代碼定義了一個(gè)CollectionType的extension敏沉,其中包含了一個(gè)新的skip(_:)方法果正。這個(gè)方法會(huì)遍歷collection中的內(nèi)容并每次“跳過(guò)”特定個(gè)數(shù)的元素,最終返回那些沒(méi)有被“跳過(guò)”的元素的collection赦抖。
CollectionType是一個(gè)protocol舱卡。Swift中一些例如數(shù)組(arrays)和字典(Dictionaries)都實(shí)現(xiàn)了這個(gè)protocol辅肾。這意味著現(xiàn)在skip(_:)這個(gè)新方法適用于所有的CollectionType队萤。
擴(kuò)展自定義的Protocols
給Swift標(biāo)準(zhǔn)庫(kù)添加新行為讓人興奮。與此同時(shí)矫钓,我們也可以利用標(biāo)準(zhǔn)庫(kù)的protocol extensions給自定義的類型定義默認(rèn)行為要尔。
修改Bird protocol使得它滿足BooleanType protocol:
protocol Bird: BooleanType {
滿足BooleanType意味著這個(gè)類型要擁有一個(gè)booleanValue屬性舍杜,讓它能夠像一個(gè)布爾值一樣被使用。這是不是表示我們要給每個(gè)Bird類型都添加這個(gè)屬性呢赵辕?
當(dāng)然不需要既绩,有更簡(jiǎn)單的方法。添加如下代碼到Bird的定義中:
extension BooleanType where Self: Bird {
var boolValue: Bool {
return self.canFly
}
}
這個(gè)extension會(huì)讓canFly屬性代表每個(gè)Bird類型的布爾值还惠。
為了測(cè)試一下效果饲握,添加如下代碼到playground的末尾:
if UnladenSwallow.African {
print("I can fly!")
} else {
print("Guess I’ll just sit here :[")
}
你應(yīng)該看到“I can fly”出現(xiàn)在了輔助編輯器上。但更加值得注意的是蚕键,你剛剛在if條件語(yǔ)句中使用了African Unladen Swallow救欧!
對(duì)Swift標(biāo)準(zhǔn)庫(kù)的影響
你已經(jīng)了解了protocol extensions在拓展自定義以及外部代碼功能方面是多么好用。而Swift團(tuán)隊(duì)用protocol extensions來(lái)改善Swift標(biāo)準(zhǔn)庫(kù)的方法可能會(huì)更加讓你感到驚奇锣光。
Swift相當(dāng)提倡函數(shù)式編程笆怠,在標(biāo)準(zhǔn)庫(kù)中引入了很多范例,包括map誊爹,reduce和filter等蹬刷。這些方法被應(yīng)用在很多CollectionType中,比如Array:
// 計(jì)算數(shù)組中有多少個(gè)字符
["frog", "pants"].map { $0.length }.reduce(0) { $0 + $1 } // 返回 9
對(duì)數(shù)組調(diào)用map方法會(huì)返回另一個(gè)數(shù)組频丘,而reduce則將結(jié)果歸約成一個(gè)最終值9办成。
在這個(gè)例子中,map和reduce都是Swift標(biāo)準(zhǔn)庫(kù)的一部分搂漠。如果你按下cmd同時(shí)點(diǎn)擊map诈火,你可以看到具體的方法定義。
在Swift 1.2中状答,定義看起來(lái)是這樣的:
// Swift 1.2
extension Array : _ArrayType {
/// 返回一個(gè) `Array`, 其中包含調(diào)用的結(jié)果
/// `transform(x)` 作用于每個(gè)`self`的`x`元素
func map<U>(transform: (T) -> U) -> [U]
}
這表明在Swift 1.2中冷守,map需要在每個(gè)Swift標(biāo)準(zhǔn)庫(kù)的CollectionType中被重定義!這是因?yàn)榧词?strong>Array和Range都是CollectionType惊科,結(jié)構(gòu)體(struct)不能有子類拍摇,也不能有共享的方法實(shí)現(xiàn)。
這不僅僅是Swift標(biāo)準(zhǔn)庫(kù)的實(shí)現(xiàn)細(xì)節(jié)馆截,這為我們使用Swift類型增加了限制充活。
下面的泛型方法接受一個(gè)Flyable的CollectionType,返回其中airspeedVelocity最高的元素蜡娶。
func topSpeed<T: CollectionType where T.GeneratorType: Flyable (collection: T) -> Double {
collection.map { $0.airspeedVelocity }.reduce { max($0, $1)}
}
在Swift 1.2中混卵,沒(méi)有protocol extensions,這實(shí)際上會(huì)帶來(lái)編譯錯(cuò)誤窖张。map和reduce只存在于定義好的具體類型當(dāng)中幕随,對(duì)于任意的CollectionType是沒(méi)有效果的。
在Swift 2中宿接,得益于protocol extensions赘淮,map的定義變成了如下:
// Swift 2.0
extension CollectionType {
/// 返回一個(gè) `Array`辕录,包含把 `transform`映射到
/// `self`上的結(jié)果.
///
/// - 復(fù)雜度: O(N).
func map<T>(@noescape transform: (Self.Generator.Element) -> T) -> [T]
}
即使你在Swift開(kāi)源之前暫時(shí)不能看到源代碼(譯者注:現(xiàn)在已經(jīng)開(kāi)源),所有的CollectionType現(xiàn)在都擁有了map的默認(rèn)實(shí)現(xiàn)梢卸!
將下面之前提到過(guò)的泛型方法加入到playground中:
func topSpeed<T: CollectionType where T.Generator.Element == Flyable>(c: T) -> Double {
return c.map { $0.airspeedVelocity }.reduce(0) { max($0, $1) }
}
map和reduce方法現(xiàn)在可以用于你的Flyable集合實(shí)例了走诞。把下面的代碼加入playground,你現(xiàn)在終于可以回答它們之中誰(shuí)是最快的了:
let flyingBirds: [Flyable] =
[UnladenSwallow.African,
UnladenSwallow.European,
SwiftBird(version: 2.0)]
topSpeed(flyingBirds) // 2000.0
結(jié)果毫無(wú)疑問(wèn)蛤高,不是么蚣旱?:]
下一步該做什么
可以在這里下載本教程完整的playground代碼。
通過(guò)搭建簡(jiǎn)單的protocols并用protocol extensions拓展它們戴陡,你已經(jīng)見(jiàn)識(shí)過(guò)了POP的強(qiáng)大之處姻锁。使用默認(rèn)實(shí)現(xiàn),你可以讓已有的protocols擁有簡(jiǎn)明且自動(dòng)化的行為猜欺,類似基類但勝在能應(yīng)用于struct和enum類型位隶。
更進(jìn)一步的,protocol extensions不僅能擴(kuò)展自定義的protocols开皿,還可以擴(kuò)展Swift標(biāo)準(zhǔn)庫(kù)涧黄,Cocoa以及Cocoa Touch中的protocols。
想要對(duì)Swift 2的新特性進(jìn)行總覽的話赋荆,可以閱讀我們的what's new in Swift 2.0笋妥,或者移步官方博客Apple’s Swift blog。
Apple’s developer portal上有個(gè)很棒的WWDC視頻推薦:Protocol Oriented Programming窄潭。想要深度了解POP理論的同學(xué)可以看看春宣。
如果還有什么問(wèn)題,歡迎在原文以及這里的評(píng)論區(qū)進(jìn)行討論嫉你。
寫在最后:本文中一些不影響文章理解的專業(yè)名詞沒(méi)有進(jìn)行翻譯月帝,比如protocol和extensions,我盡量避免使用諸如“協(xié)議拓展”之類的字眼幽污,一是避免和大家頭腦中的中文版本產(chǎn)生偏差嚷辅,二是對(duì)于這些詞匯盡量標(biāo)準(zhǔn)化比較好。POP是翻譯成“面向接口編程”還是“面向協(xié)議編程”糾結(jié)了很久距误,還是感覺(jué)“接口”用起來(lái)更容易被理解簸搞。第一次翻譯技術(shù)文章,希望大家能提出一些建設(shè)性意見(jiàn):-)