Swift的面向协议编程

作者: 苏格拉木有底oo | 来源:发表于2016-12-08 17:23 被阅读582次

    看到了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
    

    还用说😉 .

    后话: 终于翻译完了,累成狗🐶 . 看在我这么拼的份上,转载请注明出处:]

    相关文章

      网友评论

      • daniel_chan:BooleanType 协议在 swift3.0中已经删除了,所以上面的代码不能运行
        苏格拉木有底oo:@daniel_chan 是的,但是查了查并没有找到BooleanType的替换类型, 楼上如果找到可以分享给大家

      本文标题:Swift的面向协议编程

      本文链接:https://www.haomeiwen.com/subject/sjrxmttx.html