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")
复制代码
相关文章
相关标签/搜索