[譯]Swift2的面向接口編程(Protocol-Oriented Programming)

原文來(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屑迂,structenum拓展功能。而在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立宜。它有namecanFly兩個(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í)滿足BirdFlyable兩個(gè)protocol播演。airspeedVelocity作為一個(gè)computed property冀瓦,由flappyFrequencyflappyAmplitude計(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 }
}

PenguinBird是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跟束。
  刪除FlappyBirdSwiftBird定義里面的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í)滿足BirdFlyable遭铺,所以直接擁有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誊爹,reducefilter等蹬刷。這些方法被應(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è)例子中,mapreduce都是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è)FlyableCollectionType,返回其中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ò)誤窖张。mapreduce只存在于定義好的具體類型當(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) }
}

mapreduce方法現(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):-)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末准潭,一起剝皮案震驚了整個(gè)濱河市趁俊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌刑然,老刑警劉巖寺擂,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡沽讹,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門武鲁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)爽雄,“玉大人衬廷,你說(shuō)我怎么就攤上這事尺铣∧钩拢” “怎么了凡泣?”我有些...
    開(kāi)封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵座韵,是天一觀的道長(zhǎng)门驾。 經(jīng)常有香客問(wèn)我呐赡,道長(zhǎng)倾芝,這世上最難降的妖魔是什么憔涉? 我笑而不...
    開(kāi)封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任订框,我火速辦了婚禮,結(jié)果婚禮上兜叨,老公的妹妹穿的比我還像新娘穿扳。我一直安慰自己,他們只是感情好国旷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布矛物。 她就那樣靜靜地躺著,像睡著了一般跪但。 火紅的嫁衣襯著肌膚如雪履羞。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天屡久,我揣著相機(jī)與錄音忆首,去河邊找鬼。 笑死被环,一個(gè)胖子當(dāng)著我的面吹牛雄卷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蛤售,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼丁鹉,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了悴能?” 一聲冷哼從身側(cè)響起揣钦,我...
    開(kāi)封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎漠酿,沒(méi)想到半個(gè)月后冯凹,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年宇姚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了匈庭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡浑劳,死狀恐怖阱持,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情魔熏,我是刑警寧澤衷咽,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站蒜绽,受9級(jí)特大地震影響镶骗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜躲雅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一鼎姊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧相赁,春花似錦此蜈、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至跺嗽,卻和暖如春战授,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背桨嫁。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工植兰, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人璃吧。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓楣导,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親畜挨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子筒繁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容