Swift面向協議編程(POP)

在WWDC15上,蘋果宣佈Swift是世界上第一門面向協議編程(POP)語言。相比與傳統的面向對象編程 (OOP),POP 顯得更加靈活。RxSwift、ReactorKit 核心也是面向協議編程的。編程

1、什麼是POP

要弄清楚什麼是面向協議(POP),咱們應該先知道什麼是Swift協議? 咱們定義一個簡單的Swift協議以下:swift

protocol Runable {
    var name: String { get }
    func run()
}
複製代碼

代碼中定義名爲Runable協議,包含一個name屬性,以及一個run方法的定義 所謂協議,就是一組屬性和/或方法的定義,而若是某個具體類型想要遵照一個協議,那它須要實現這個協議所定義的全部這些內容。協議實際上作的事情不過是"關於實現的約定"。bash

面向協議編程(POP)是Swift2.0引入的一種新的編程範式。POP就是經過協議擴展,協議繼承和協議組合的方式來設計須要編寫的代碼。組件化

2、爲何要使用面向協議編程(POP)

Swift是一門面向對象的語言,類已經知足咱們全部的需求,功能也十分強大。爲何還要使用POP?單元測試

首先在Swift中,值類型優先於類。然而,面向對象的概念不能很好地與結構體和枚舉一塊兒工做: 由於結構體和枚舉不可以被繼承。所以,做爲面向對象的一大特徵—繼承就不可以應用於值類型了。測試

再者,實際開發工程中,咱們常常會遇到以下場景: 假設咱們有一個ViewController,它繼承自UIViewController,咱們向其新添加一個方法 customMethodui

class ViewController: UIViewController {
    //新添加
    func customMethod() {
    }
}
複製代碼

這個時候咱們有另一個繼承自UITableViewControllerOtherViewController,一樣也須要向其添加方法customMethodspa

class OtherViewController: UITableViewController {
    //新添加
    func customMethod() {
    }
}
複製代碼

這裏就存在一個問題:很難在不一樣繼承關係的類裏共用代碼。 咱們的關注點customMethod位於兩條繼承鏈 UIViewController -> ViewCotrollerUIViewController -> UITableViewController -> AnotherViewController 的橫切面上。面向對象是一種不錯的抽象方式,可是確定不是最好的方式。它沒法描述兩個不一樣事物具備某個相同特性這一點。在這裏,特性的組合要比繼承更貼切事物的本質。設計

這種狀況,咱們有以下幾種方法解決:code

  • Copy & Paste 這是一個糟糕的解決方案,咱們應該儘可能避免這種作法。

  • BaseViewController 在一個繼承自 UIViewControllerBaseViewController 上添加須要共享的代碼,或者在 UIViewController 上添加 extension。這是目前不少人經常使用的解決方法,可是若是不斷這麼作,會讓所謂的BaseViewController 很快變成垃圾堆。職責不明確,任何東西都能扔進 Base,你徹底不知道哪些類走了 Base,而這個「超級類」對代碼的影響也會不可預估。

  • 依賴注入 經過外界傳入一個帶有 customMethod的對象,用新的類型來提供這個功能。這是一個稍好的方式,可是引入額外的依賴關係,可能也是咱們不太願意看到的。

  • 多繼承 固然,Swift 是不支持多繼承的。不過若是有多繼承的話,咱們確實能夠從多個父類進行繼承,並將customMethod添加到合適的地方,但這又會帶來其餘問題。

總的來講,面向協議編程(POP) 帶來的好處以下:

  • 結構體、枚舉等值類型也可使用
  • 以繼承多個協議,彌補 swift 中類單繼承的不足
  • 加強代碼的可擴展性,減小代碼的冗餘
  • 讓項目更加組件化,代碼可讀性更高
  • 讓無需的功能代碼組成一個功能塊,更便於單元測試。

3、使用POP解決上述問題

定義一個含有customMethod的協議ex:

protocol ex {
    func customMethod();
}
複製代碼

在實際類型遵照這個協議:

extension ViewController :ex {
    func customMethod() {
        //
    }
}
extension OtherViewController : ex {
    func customMethod() {
        //
    }
}
複製代碼

這樣的實現和Copy & Paste的方式同樣,必需要在ViewControllerOtherViewController都寫一遍。有什麼方法能夠解決這問題呢?那就是協議擴展

能夠在 extension ex 中爲 customMethod 添加一個實現:

extension ex {
    func customMethod() {
        //
    }
}
複製代碼

有這個協議擴展,只須要聲明ViewControllerOtherViewController遵循ex,就能夠直接使用customMethod了。

4、協議的特性及使用

協議擴展

  • 1.提供協議方法的默認實現和協議屬性的默認值,從而使它們成爲可選;符合協議的類型能夠提供本身的實現,也可使用默認的實現。
  • 2.添加協議中未聲明的附加方法實現,而且實現協議的任何類型均可以使用到這些附加方法。這樣就能夠給遵循協議的類型添加特定的方法
protocol Entity {
    var name: String {get set}
    static func uid() -> String
}

extension Entity {
    static func uid() -> String {
        return UUID().uuidString
    }
}

struct Order: Entity {
    var name: String
    let uid: String = Order.uid()
}
let order = Order(name: "My Order")
print(order.uid)
複製代碼

協議繼承

協議能夠從其餘協議繼承,而後在它繼承的需求之上添加功能,所以能夠提供更細粒度和更靈活的設計。

protocol Persistable: Entity {
    func write(instance: Entity, to filePath: String)
    init?(by uid: String)
}

struct InMemoryEntity: Entity {
    var name: String
}

struct PersistableEntity: Persistable {
    var name: String
    func write(instance: Entity, to filePath: String) { // ...
    }  
    init?(by uid: String) {
        // try to load from the filesystem based on id
    }
}
複製代碼

協議的組合

類、結構體和枚舉能夠符合多個協議,它們能夠採用多個協議的默認實現。這在概念上相似於多繼承。這種組合的方式不只比將全部須要的功能壓縮到一個基類中更靈活,並且也適用於值類型。

struct MyEntity: Entity, Equatable, CustomStringConvertible {
    var name: String
    // Equatable
    public static func ==(lhs: MyEntity, rhs: MyEntity) -> Bool {
        return lhs.name == rhs.name
    }
    // CustomStringConvertible
    public var description: String {
        return "MyEntity: \(name)"
    }
}
let entity1 = MyEntity(name: "42")
print(entity1)
let entity2 = MyEntity(name: "42")
assert(entity1 == entity2, "Entities shall be equal")
複製代碼
相關文章
相關標籤/搜索