Swift的面向協(xié)議編程

看到了Raywederlich的這篇文章,覺得寫得簡單易懂, 為了更加深刻的理解也希望把這篇文章推廣給更多的人,在此翻譯出來:

在WWDC2015中,蘋果發(fā)布了Swift 2.0,它包含了一些新的特性 以便于我們更好地編寫代碼.
這些激動人心的特性之一就是 協(xié)議擴展. 在Swift的第一個版本中, 我們可以為 , 結(jié)構(gòu)體枚舉 添加擴展. 現(xiàn)在在Swift 2.0中,你同樣可以為一個 協(xié)議 添加擴展.
它乍看上去像是一個不那么重要的特性,但是協(xié)議擴展是一個非常強大的特性并且可以改變你編碼的方式. 在這個教程中, 我們將會探索創(chuàng)建和使用協(xié)議擴展的方式,蘋果開放的新技術(shù)以及面向協(xié)議編程模式.
你也會看到Swift團隊是如何用協(xié)議擴展來完善Swift標準庫的.

開始

創(chuàng)建一個playground, 名字也替你想好了,不用糾結(jié),就叫SwiftProtocols吧.你可以選擇任何平臺,因為這個教程中的代碼是跨平臺的.
playground打開之后,添加以下代碼:

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

protocol Flyable {
    var airspeedVelocity: Double { get }
}

這幾句代碼定義了一個簡單的 Bird 協(xié)議, 它擁有 namecanFly 兩個屬性; 和一個 Flyable 協(xié)議,有一個 airspeedVelocity 屬性.

在面向?qū)ο蟮氖澜缋? 你可能會定義 Flyable 作為基類,然后讓 Bird 還有其他能飛的東西繼承自它, 比如飛機.但是在面向協(xié)議的世界中, 一些事物是以協(xié)議為起始的.

在下面定義真正類型的時候你將會看到面向協(xié)議是如何讓整個系統(tǒng)更加靈活的.

定義遵循協(xié)議的類型

添加下面的結(jié)構(gòu)體到playground的底部:

struct FlappyBird: Bird, Flyable {
    let name: String
    let flappyAmplitude: Double
    let flappyFrequency: Double
    let canFly = true

    var airspeedVelocity: Double {
        return 3 * flappyFrequency * flappyAmplitude
    }
}

上面的??代碼定義了一個新的結(jié)構(gòu)體 FlappyBird , 它遵循了 BirdFlyable 協(xié)議.它的 airspeedVelocity 是一個跟 flappyFrequencyflappyAmplitude 有關(guān)的計算型屬性. 既然是* flappy (意為飛揚的), 它的 canFly *屬性必然為true. :]

下一步, 添加下面的兩個結(jié)構(gòu)體到playground的底部:

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* (鳥類), 但是是不能飛的鳥類.A-ha~ 辛虧我們沒有使用繼承關(guān)系,把* Bird* 寫成類繼承自Flyable,讓所有的鳥類都必須會飛,要不然企鵝得有多蛋疼.一個* SwiftBird (swift在英文中是雨燕的意思,因為雨燕飛的很快,所以swift也有迅速之意)當(dāng)然是擁有高的 airspeed velocity* 非常快的鳥(傲嬌臉).
現(xiàn)在你已經(jīng)能看到一些代碼冗余.每一個遵守* Bird* 協(xié)議的類型都必須聲明它是不是* canFly* , 盡管你的系統(tǒng)中已經(jīng)有了一個* Flyable* 的概念.

用默認行為來擴展協(xié)議

你可以用協(xié)議擴展定義一個協(xié)議的默認行為.添加下面的代碼到* Bird* 協(xié)議下面:

extension Bird where Self: Flyable {
    // Flyable birds can fly!
    var canFly: Bool { return true }
}

這個* Bird* 擴展讓所有同樣遵循了 Flyable 協(xié)議的類型的canFly屬性返回true. 也就是說, 遵守了 Flyable 的* Bird* 都不用再明確地聲明* canFly* 了.

protocols-extend-480x280.png

Swift1.2在if - let 的綁定使用中引入了where語法.Swift2.0讓我們在我們的協(xié)議擴展需要一個約束條件時同樣能夠使用它.
在* FlappyBird* 和 * SwiftBird* 結(jié)構(gòu)體中刪除* let canFly = true* . playgroud運行良好, 因為協(xié)議擴展已經(jīng)替你處理了那個需求.

為什么不用基類?

協(xié)議擴展和默認實現(xiàn)有些像基類或者其他語言中的抽象類, 但是它在Swift中有一些核心優(yōu)勢:

  • 類型可以遵循多個協(xié)議,所以他們可以有很多默認行為. 不像其他語言支持的類的多繼承, 協(xié)議擴展不會帶來額外的狀態(tài)(這里因為對多繼承不是很了解,所以直接翻譯了,有人給我推薦了喵神的這篇文章,提到了多繼承的菱形缺陷, 不知道這里所謂的額外狀態(tài)是不是指菱形缺陷,比較懂的朋友還請指點一二)
  • 除了類,結(jié)構(gòu)體和枚舉也可以使用協(xié)議.而基類和繼承只局限于類,結(jié)構(gòu)體和枚舉用不了

換句話說,協(xié)議擴展讓值類型可以擁有默認行為.

上面我們已經(jīng)說了協(xié)議擴展在結(jié)構(gòu)體中的使用,下面來看看枚舉, 將下面的代碼加到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!")
        }
    }
}

只是換了個類型而已,和結(jié)構(gòu)體沒太大區(qū)別,不細述.

你不會真的認為這篇教程用到的* airspeedVelocity* 不是用的蒙提派森的梗吧??? (這里解釋一下,* Monty Phython* 又譯為巨蟒劇團吮蛹、蒙提巨蟒牙躺、踎低噴飯蹭越,是英國的一組超現(xiàn)實幽默表演團體.而上面的* UnladenSwallow* 枚舉是源自于他們一個劇的對話,感興趣的可以去搜下)

擴展協(xié)議

協(xié)議擴展最常用的就是擴展外部協(xié)議, 不論它是定義在Swift標準庫中還是第三方庫中
將下面??的代碼加到playground的底部:

extension Collection {
    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() Swift3中作了以下改變
            index = self.index(after: index)
            //Swift3中不允許用i++了
            i += 1
        } while (index != self.endIndex)
        
        return result
    }
}

這段代碼定義了* CollectionType* (Swift3改成了Collection)的一個擴展,里面添加了* skip(_:)* 方法, 這個方法會跳過所有給定條件的元素,然后返回沒有被跳過的元素集合.
CollectionType 是一個被Swift中比如數(shù)組,字典這樣的集合類型所遵循的協(xié)議.這意味著新增的這個方法所有的集合類型都可以用.
又來啦,把下面的代碼添加到playground底部:

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

bunchaBirds.skip(skip: 3)

這里我們定義了一個鳥類的數(shù)組,因為數(shù)組遵守了* CollectionType* 的協(xié)議,所以也能調(diào)用* skip(_:)* 方法.

擴展你自己的協(xié)議

我們不光可以像上面一樣給標準庫中的協(xié)議擴展方法,還可以添加默認行為.
修改* Bird* 協(xié)議生命讓它遵循* BooleanType* 協(xié)議:

protocol Bird: BooleanType {

遵守* BooleanType* 協(xié)議意味著你的類型得有一個* boolValue* . 難道我們要把這個屬性添加到每一個* Bird* 類型嗎???
當(dāng)然不,將下面的代碼添加到* Bird* 定義下面:

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

這個擴展讓* canFly* 屬性代表了每個* Bird* 類型的布爾值.
看這個是否成立,我們試試下面的代碼,同樣加到playground底部:

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

非布爾值是不能直接在if后面做true or false的判斷的, 但是這里卻可以了,就是因為* Bird* 遵循了* BooleanType* , 而* UnladenSwallow.African* 的* canFly* 值是true, 所以它的* boolValue* 也是true.

對于Swift標準庫的影響

上面我們已經(jīng)看到協(xié)議擴展怎樣幫助我們自定義和擴展我們的代碼功能.更加讓你感到驚訝的會是Swift項目組如何運用協(xié)議擴展來完善Swift標準庫.
Swfit引入了map, reduce, 和 filter 方法來促進函數(shù)式編程.這些方法用在集合類型中, 比如數(shù)組:

//計算數(shù)組中所有元素字符數(shù)之和
let result = ["frog","pants"].map { $0.lengthOfBytes(using: .utf8) }.reduce(0) { $0 + $1 }
print(result)

調(diào)用數(shù)組的* map* 函數(shù)返回另外一個數(shù)組,這個數(shù)組里面盛放的是原數(shù)組的每個元素字符數(shù),即[4,5], 這個數(shù)組又調(diào)用了 reduce 函數(shù)來計算二者之和,結(jié)果返回9.

Cmd-Click進入 map 函數(shù)源碼可以看到它的定義.
Swfit 1.2中如下:

// Swift 1.2
extension Array : _ArrayType {
      /// Return an `Array` containing the results of calling 
      /// `transform(x)` on each element `x` of `self` 
      func map<U>(transform: (T) -> U) -> [U]
}

這里 map 函數(shù)是定義在 Array 的extension中的, 可是這些功能性的函數(shù)不止能被 Array 使用, 它們可以被任何集合類型所調(diào)用, 那么Swift 1.2中是怎么實現(xiàn)的呢?
如果你用一個 Range 類型調(diào)用 map 方法, 然后Cmd-Click map函數(shù)進入源碼,能看到下面的代碼:

// Swift 1.2
extension Range { 
      /// Return an array containing the results of calling 
      /// `transform(x)` on each element `x` of `self`. 
      func map<U>(transform: (T) -> U) -> [U]
}

結(jié)果是Swift 1.2中,所有的集合類型都要定義一遍 map 函數(shù), 這是因為雖然 ArrayRange 都是集合類型,但是結(jié)構(gòu)體是不能被繼承的.
這不是一個小差別,它會限制你使用Swift的類型.
下面的泛型函數(shù)適用于那些遵循了 Flyable 的集合類型,并返回一個只有一個元素的數(shù)組, 這個元素就是集合類型中所有元素的最大 airspeedVelocity :

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

Swift 1.2中這個函數(shù)會報錯, mapreduce 等函數(shù)只能被標準庫中已經(jīng)定義的集合類型使用, 像這里我們自己定義的遵守 Flyable 的集合類型是不能使用這些函數(shù)的.

Swift 2.0中, map 函數(shù)是這樣定義的:

// Swift 2.0
extension CollectionType { 
    /// Return an `Array` containing the results of mapping `transform` 
    /// over `self`. 
    /// 
    /// - Complexity: O(N). 
    func map<T>(@noescape transform: (Self.Generator.Element) -> T) -> [T]
}

這樣所有的集合類型都能使用 map 函數(shù)了.

將上面的泛型函數(shù)加到playground的底部:

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

運行良好?? . 現(xiàn)在我們可以看看到底這些鳥類中誰是最快的! :]

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

topSpeed(flyingBirds) // 2000.0

還用說?? .

后話: 終于翻譯完了,累成狗?? . 看在我這么拼的份上,轉(zhuǎn)載請注明出處:]

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末截亦,一起剝皮案震驚了整個濱河市秕岛,隨后出現(xiàn)的幾起案子猜惋,更是在濱河造成了極大的恐慌缺前,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件监氢,死亡現(xiàn)場離奇詭異布蔗,居然都是意外死亡,警方通過查閱死者的電腦和手機浪腐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門纵揍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人议街,你說我怎么就攤上這事泽谨。” “怎么了特漩?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵隔盛,是天一觀的道長。 經(jīng)常有香客問我拾稳,道長吮炕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任访得,我火速辦了婚禮龙亲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘悍抑。我一直安慰自己鳄炉,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布搜骡。 她就那樣靜靜地躺著拂盯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪记靡。 梳的紋絲不亂的頭發(fā)上谈竿,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天,我揣著相機與錄音摸吠,去河邊找鬼空凸。 笑死,一個胖子當(dāng)著我的面吹牛寸痢,可吹牛的內(nèi)容都是我干的呀洲。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼道逗!你這毒婦竟也來了兵罢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤滓窍,失蹤者是張志新(化名)和其女友劉穎卖词,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贰您,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年拢操,在試婚紗的時候發(fā)現(xiàn)自己被綠了锦亦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡令境,死狀恐怖杠园,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情舔庶,我是刑警寧澤抛蚁,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站惕橙,受9級特大地震影響瞧甩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜弥鹦,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一肚逸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧彬坏,春花似錦朦促、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至幻赚,卻和暖如春禀忆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背落恼。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工油湖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人领跛。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓乏德,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子喊括,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,055評論 2 355

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