Swift的面向協議編程

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

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

開始

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

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

protocol Flyable {
    var airspeedVelocity: Double { get }
}

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

在面向對象的世界里, 你可能會定義 Flyable 作為基類,然后讓 Bird 還有其他能飛的東西繼承自它, 比如飛機.但是在面向協議的世界中, 一些事物是以協議為起始的.

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

定義遵循協議的類型

添加下面的結構體到playground的底部:

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

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

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

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

用默認行為來擴展協議

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

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

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

protocols-extend-480x280.png

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

為什么不用基類?

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

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

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

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

只是換了個類型而已,和結構體沒太大區別,不細述.

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

擴展協議

協議擴展最常用的就是擴展外部協議, 不論它是定義在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中比如數組,字典這樣的集合類型所遵循的協議.這意味著新增的這個方法所有的集合類型都可以用.
又來啦,把下面的代碼添加到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)

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

擴展你自己的協議

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

protocol Bird: BooleanType {

遵守* BooleanType* 協議意味著你的類型得有一個* boolValue* . 難道我們要把這個屬性添加到每一個* Bird* 類型嗎???
當然不,將下面的代碼添加到* 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標準庫的影響

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

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

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

Cmd-Click進入 map 函數源碼可以看到它的定義.
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 函數是定義在 Array 的extension中的, 可是這些功能性的函數不止能被 Array 使用, 它們可以被任何集合類型所調用, 那么Swift 1.2中是怎么實現的呢?
如果你用一個 Range 類型調用 map 方法, 然后Cmd-Click map函數進入源碼,能看到下面的代碼:

// 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]
}

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

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

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

Swift 2.0中, map 函數是這樣定義的:

// 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 函數了.

將上面的泛型函數加到playground的底部:

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

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

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

topSpeed(flyingBirds) // 2000.0

還用說?? .

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容