面向協議編程介紹(一切始于協議)

本篇文章翻譯自:Introducing Protocol-Oriented Programming in Swift 2)
原作:[Erik kerber]
(https://www.raywenderlich.com/u/kerber) on June 25, 2015


注: 這篇tutorial需要XCode 7, Swift2, 這個秋天會發布測試版。你也可以在 Apple's developer portal下載最新版本。
WWDC 2015, 蘋果發布了swift 2,宣布了swift語言第二次重大修訂,其中包括了幾個新的語言特征,以幫助你提升編碼方式。
在這些的新特征中,最令人興奮的是協議擴展。在swift第一個版本,你可以擴展已經存在的類,結構體,和枚舉類型的功能。現在有了swift 2,你也可以擴展協議。
可能剛開始你會覺得這是一個微不足道的特征,但是協議擴展真的能量巨大,且可以轉變你的編碼方式。在這篇turorial,你會探索創建,使用協議擴展的方法,還有新技術和面向協議編程模式(以下簡稱:POP)。
你將會看到Swift團隊是怎樣使用協議擴展以提升swift標準庫,和協議擴展怎樣影響你的代碼。

開始

開始在XCode中創建一個新的Playground, 選擇File\New\Playground...,然后命名Playground為SwiftProtocols。你可以選擇任何平臺,因為這篇tutorial所有的代碼跟平臺無關。點擊Next選擇你要保存的路徑,然后點擊Create.
Playground打開后,我們添加如下代碼:

protocol Bird {
     var name: String { get }
          var canFly:Bool { get }
     }

     protocol Flyable {
         var airspeedVelocity: Double { get }
     }
}

這里簡單的定義了一個擁有2個屬性name, 和canFly的Bird協議類型,還有一個Flyable類型,它定義了airspeedVelocity屬性。
在pre-protocol時期,你可能會把Flyable當做基類,然后依賴繼承去定義Bird還有其他能fly的東西,例如:灰機。請記住:一切始于協議!

定義遵守協議的類型

在Playground下方添加struct的定義:

struct FlappyBird: Bird, Flyable {    
  let name: String       
  let flappyAmplitude: Double   
  let flappyFrequency: Double    
  let canFly = true      
  var airspeedVelocity: Double {          
      return 3 * flappyFrequency * flappyAmplitude  
  }
}

這里定義了一個新的結構體FlappyBird, 它遵守Bird和Flyable協議,airspeedVelocity為計算屬性。canFly屬性設置為true。
接下來,在playground下面添加以下2個結構體的定義:

struct Penguin: Bird {     
   let name: String     
   let canFly: Bool
}

struct SwiftBird: Bird, Flyable {  
     var name: String { 
        return "swift \(version)" 
     }    
     let version: Double      
     let canFly = true     
     var airspeedVelocity: Double { 
          return 2000.0
     }
}

企鵝是鳥類,但不會飛。哈--- 還好你沒有使用繼承,讓鳥類都能飛。海燕當然有非常快的飛行速度。
你可能已經看到一些冗余。盡管在你的結構中已經有了Flayable的概念,每一種Bird類型還必須聲明是否canFly。

用默認實現擴展協議

使用協議擴展,你可以為協議定義默認行為。在Bird協議下添加如下代碼:

extension Bird where Self: Flyable {     
   var canFly: Bool { return true }
}

這里定義Bird的一個擴展,當類型也為Flyable時,設置為canFly設置默認行為true。換句話說,任何一個遵守Flyable的Bird類型不必在顯示聲明。

i wanna everything automatic

swift 1.2 引入了where語法,之后swift 2使用where語法給協議擴展添加約束(Self作用是指,當Bird類型同時也遵守Flyable協議,canFly屬性才能生效)
現在從FlappyBird和SwiftBird結構體聲明中刪除let canFly = true。你會發現playground成功編譯了,因為協議擴展幫你處理了協議的要求。

為什么不用基類

協議擴展和其默認實現好像跟其他語言的基類或者抽象類很相似,但是swift具有一下幾個關鍵優勢:

  • 因為類型可以遵守多個協議,可以擁有多個協議默認實現的功能。不像其他編程語言支持的類的多繼承,協議擴展不會引入額外的狀態。
  • 協議可以被類,結構體和枚舉類型遵守。基類和繼承受限于類類型(class類型)
    換句話說,協議擴展為值類型,而不僅僅是為類類型提供了定義默認行為的能力。
    你已經在結構體中見識過這種行為;接下來在playground添加枚舉定義:
enum UnladenSwallow: Bird, Flyable {
    case Africancase
    case Europeancase
    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!")}
    }
}

像其他值類型一樣,遵守Bird和Flyable協議,你只需要定義協議要求的屬性。因為它遵守這2個協議,所以UnladenSwallow的canFly屬性也獲得了默認實現。
你真的認為該 tutorial只會包括airspeedVelocity屬性,而不會包括Monty Python的引用。(哈,可擴展。。。)

擴展協議

可能協議擴展最普遍的用法就是擴展外部協議了,可能是定義在swift標準庫的協議,也可能是定義在第三方框架的協議。在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 += 1
        }while ( index != self.endIndex)
        return result
    }
}

在CollectionType類型上擴展了一個新的方法skip(:), 它會遍歷集合中的每一個元素,過濾掉不符合要求的元素(留下索引值能被參數skip整除對應的元素),然后返回一個過濾后的數組。
CollectionType是一個被諸如swift中數組,字典等類型遵守的協議。這就意味著這個新的行為(skip(
:) 方法)存在你APP中所有的CollectionType類型中。playground中添加如下代碼,我們來見證這一行為:

let bunchBirds: [Bird] = [
    UnladenSwallow.African,
    UnladenSwallow.European,
    UnladenSwallow.Unknown,
    Penguin(name: "King Penguin", canFly: false),
    SwiftBird(version: 2.0),
    FlappyBird(name: "Felipe", flappyAmplitude: 3.0, flappyFrequency: 20.0)
]
bunchBirds.skip(3)

這里,你定義了一個包含各種類型鳥類的數組,這些類型上文中你已經定義過。因為數組遵守CollectionType, 那么它就能用skip(_:)方法。

擴展你自己的協議

也許讓你興奮的是你可以像給swift標準庫添加新的行為一樣,你也可以給自己的協議定義新的行為。
修改Bird協議聲明,使之遵守BooleanType協議:

protocol Bird: BooleanType {
    var name: String { get }
    var canFly: Bool { get }
}

遵守BooleanType協議意味著你的類型需要有一個Bool類型的 boolValue屬性。那么是不是意味著你必須給現在或者之后的每一個Bird類型都添加這么個屬性呢?
當然,我們有更容易的辦法———協議擴展。在Bird定義下添加如下代碼:

extension BooleanType where Self: Bird {
    var boolValue: Bool {return self.canFly}
}

這個擴展讓canFly屬性代表了每一個Bird類型的Boolean 值。playground中添加如下代碼,一起來見證奇跡:

if UnladenSwallow.African {
    print("I can fly!")
}else {
    print("Guess I'll just sit here")
}

你會看到"I can fly!" 出現在輔助編輯器上。但是這里你僅是在if 語句中使用了UnladenSwallow。(你可以挖掘更多用法!!!)

對swift標準庫的影響

你已經見證過了協議擴展是多么棒--- 你可以使用它自定義和擴展你自己的代碼的能力,還有你APP之外的協議(標準庫和第三方框架協議)。也許你會好奇,swift團隊是怎么使用協議擴展來提升swift標準庫的。
swift通過向標準庫中添加諸如map, reduce和filter等高階函數來提升函數式編程范式。這些方法存在不同CollectionType類型中,例如:Array:

["frog", "pants"].map{ $0.length}.reduce(0){$0 + $1}

array調用map會返回另一個array, 新的array調用reduce來歸納結果為9.(map會遍歷數組中每一個元素,并且返回由數組長度組成的數組,reduce把新數組中元素長度做加運算)
這種情況下,map和reduce作為swift標準庫的一部分被包含在Array中,如果你按下cmd,點擊map,你可以看到它怎么定義的:

extension Array : _ArrayType {
         func map(transform: (T) -> U) -> [U]
 }

這里的map函數被定義為Array的一個擴展. swift的函數范式工作范圍遠不止Array, 它在任意的CollectionType類型上都能工作。所以swift 1.2下是怎么工作的呢?
按下cmd,點擊Range的map函數,你將看到以下定義:

extension Range {
      func map(transform: (T) -> U) -> [U]
}

這些表明在swift 1.2中, swift標準庫中的各種CollectionType類型需要重新定義map函數。因為盡管Array和Range都是CollectionType,但是結構體不能被子類化,也沒有統一的實現。
這并不僅僅是swift標準庫做出的細微差別,也對你使用swift類型做出約束。
下面的泛型函數接受一個遵守Flyable協議的CollectionType類型參數,返回airspeedVelocity最大的元素。

func topSpeed(collection: T) -> Double {    
         collection.map { $0.airspeedVelocity }.reduce { max($0, $1)}
}

在swift 1.2中沒有協議擴展,這會出現編譯錯誤。map和reduce函數只會存在于預先定義的類型中,不會在任意的CollectionType都會工作。
在swift 2.0中有了協議擴展,Array和Range的map定義如下:

extension CollectionType {  
         func map(@noescape transform: (Self.Generator.Element) -> T) -> [T]
}

盡管你不能看到map源碼---至少到swift 2開源(目前已經開源了),ColletionType有map的一個默認實現,而且所有的CollectionType都能免費獲得??。
在playground下面添加早先提到的泛型函數:

func topSpeed(c: T) -> Double {   
     return c.map { $0.airspeedVelocity }.reduce(0) { max($0, $1) }
}

map和reduce函數可以用在你的Flyable實例集合中了。現在你終于可以回答他們中誰是最快的。

let flyingBirds: [Flyable] =[    
     UnladenSwallow.African,   
     UnladenSwallow.European,
     SwiftBird(version: 2.0)
]
topSpeed(flyingBirds) // 2000.

結果應該無人質疑吧??(海燕最快)

延伸閱讀

你可以在這里下載本tutorial完整playground代碼。
你已經見識過POP的威力了,創建你自己的簡單協議,然后使用協議擴展來擴展他們。有了默認實現,你可以給已經存在的協議一個統一的實現,有些像基類但是優于基類,因為它還可以應用自愛結構體和枚舉類型上。
另外,協議擴展不僅能夠勇子啊你自定義的協議上,還可以擴展swift標準庫,Cocoa, CocoaTouch中協議的默認行為。
如果想要瀏覽swift 2.0令人興奮的新特征,你可以閱讀[our "what's new in swift 2.0 article"], 或者 Apple's swift blog
你也可以在蘋果開發者入口Protocal Oriented Programming觀看WWDC Session深入理解背后的原理。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內容