Swift之面向协议编程POP

529 阅读4分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

1.什么是面向协议编程

Apple于2015年WWDC提出,Swift是世界上第一门面向协议编程的语言。它是swift2.0引入的一种新的编程范式,通过协议扩展协议继承协议组合的方式来设计编写代码。

2.面向协议编程和面向对象编程

面向协议编程POP,和面向对象编程OOP,这2个是相辅相成的,没有说一定只能用某个,更多的时候,POP能补足OOP一些设计的不足。

OOP的三大特性是封装,继承,多态。

很多时候,我们都会把一些共性的东西,提供到父类,让子类去继承父类。好处是子类可以直接使用父类的方法,不好的地方是父类的东西有可能越来越多,变得臃肿。这时候,POP就可以解决这种问题了。

我们可以通过protocol协议,对协议继续extension扩展,在扩展里面实现协议的方法,让我们的类遵循protocol协议,就可以调用协议扩展方法了。

protocol JJProtocol {
    func toast()
}

extension JJProtocol {
    func toast() {
        print("toast")
    }
}

class Cat: JJProtocol {
}

class Dog: JJProtocol {
}

do {
    let cat = Cat()
    cat.toast()
    
    let dog = Dog()
    dog.toast()
}

这样的好处是,你想要哪个类实现toast功能,就哪个类遵循这个协议就好。

3.总结POP的好处

  • 结构体,枚举等值类型不可以继承,所以可以使用这种方式
  • 增强的代码的可扩展性,在协议扩展中实现,减少代码的冗余
  • 协议可以看做是一个组件,增强可测性

4.利用协议实现前缀效果

RxSwift的核心就是面向协议编程。特别是RxCocoa里面,封装了大量关于iOS控件的使用。如按钮的button.rx.tap。点击进去可以发现,rx是一个Reactive的结构体。实现如下

public struct Reactive<Base> {
    /// Base object to extend.
    public let base: Base

    /// Creates extensions with base object.
    ///
    /// - parameter base: Base object.
    public init(_ base: Base) {
        self.base = base
    }
}

/// A type that has reactive extensions.
public protocol ReactiveCompatible {
    /// Extended type
    associatedtype ReactiveBase

    @available(*, deprecated, renamed: "ReactiveBase")
    typealias CompatibleType = ReactiveBase

    /// Reactive extensions.
    static var rx: Reactive<ReactiveBase>.Type { get set }

    /// Reactive extensions.
    var rx: Reactive<ReactiveBase> { get set }
}

extension ReactiveCompatible {
    /// Reactive extensions.
    public static var rx: Reactive<Self>.Type {
        get {
            return Reactive<Self>.self
        }
        // swiftlint:disable:next unused_setter_value
        set {
            // this enables using Reactive to "mutate" base type
        }
    }

    /// Reactive extensions.
    public var rx: Reactive<Self> {
        get {
            return Reactive(self)
        }
        // swiftlint:disable:next unused_setter_value
        set {
            // this enables using Reactive to "mutate" base object
        }
    }
}

import class Foundation.NSObject

/// Extend NSObject with `rx` proxy.
extension NSObject: ReactiveCompatible { }

如果我们也想扩展自己的功能,又担心和系统的命名冲突,这时候我们可以模拟这种写法,这里我用自己的简写jj,来实现类似rx的效果。下面我们一步一步推演。

先写一个String的扩展计算属性。

extension String {
    var numberCount: Int {
        var count = 0
        for c in self where ("0"..."9").contains(c) {
            count += 1
        }
        return count
    }
}

这时候,我们就可以写"123abc".numberCount,但这个不是我们想要的效果。参考rx,我们实现自己的结构体JJStruct,里面初始化str,让结构体的扩展实现numberCount方法。

而要做到string.jj,只需要在String的扩展中添加计算属性jj即可。

struct JJStruct {
    var str: String
    init(_ string: String) {
        self.str = string
    }
}

extension JJStruct {
    var numberCount: Int {
        var count = 0
        for c in str where ("0"..."9").contains(c) {
            count += 1
        }
        return count
    }
}

extension String {
    var jj: JJStruct { JJStruct(self) }
}

"123abc".jj.numberCount

上面的确可以实现.jj效果,但是我们又发现,如果我们想要扩展Int,或者其他的,这时候难道又要在结构体JJStruct中,添加int的变量吗,所以这里我们要把这些改成泛型。

struct JJStruct<Base> {
    var base: Base
    init(_ base: Base) {
        self.base = base
    }
}

extension JJStruct where Base == String {
    var numberCount: Int {
        var count = 0
        for c in base where ("0"..."9").contains(c) {
            count += 1
        }
        return count
    }
}

extension String {
    var jj: JJStruct<Self> { JJStruct(self) }
}

这样的话,我们其它的扩展,只要Base传相应的类型就可以了,但我们又发现,这里只支持实现对象调用,而且方法还不能加mutating修改,所以我们得优化一下。

再添加多一个静态计算属性,允许set和get。

struct JJStruct<Base> {
    var base: Base
    init(_ base: Base) {
        self.base = base
    }
}

extension JJStruct where Base == String {
    var numberCount: Int {
        var count = 0
        for c in base where ("0"..."9").contains(c) {
            count += 1
        }
        return count
    }
    
    static func test() {
        
    }
    
    mutating func test2() {
        
    }
}

extension String {
    var jj: JJStruct<Self> {
        set {}
        get {JJStruct(self)}
    }
    
    static var jj: JJStruct<Self>.Type {
        set{}
        get {JJStruct.self}
    }
    
}

"123abc".jj.numberCount
var str = "123abc"
str.jj.test2()
String.jj.test()

这时候,问题又来了,我要是还要扩展Int,还是需要var jj,这时候,我们就可以用到协议了。在协议扩展中定义计算属性,要扩展的String ,Int, Class都遵循这个协议。

struct JJStruct<Base> {
    var base: Base
    init(_ base: Base) {
        self.base = base
    }
}

protocol JJProtocol {
}

extension JJProtocol {
    var jj: JJStruct<Self> {
        set {}
        get {JJStruct(self)}
    }
    
    static var jj: JJStruct<Self>.Type {
        set{}
        get {JJStruct.self}
    }
}

extension JJStruct where Base: ExpressibleByStringLiteral {
    var numberCount: Int {
        var count = 0
        for c in base as! String where ("0"..."9").contains(c) {
            count += 1
        }
        return count
    }
    
    static func test() {
        
    }
    
    mutating func test2() {
        
    }
}

extension String : JJProtocol {}

也就是说,后面只要extension Type: JJProtocol {},该Type就是Base类型,然后我们extension JJStruct where Base == Type里面,实现想要扩展的计算属性,方法等,就可以了。看到最后的实现,对比一下rx,你会发现,和我们自己封装的是差不多的。