美文网首页swift编程开发博客积累swift
面向协议编程介绍(一切始于协议)

面向协议编程介绍(一切始于协议)

作者: matrix_lab | 来源:发表于2016-08-13 23:34 被阅读501次

    本篇文章翻译自:Introducing Protocol-Oriented Programming in Swift 2)
    原作:[Erik kerber]
    (https://www.raywenderlich.com/u/kerber) on June 25, 2015


    注: 这篇tutorial需要XCode 7, Swift2, 这个秋天会发布测试版。你也可以在 Apple's developer portal下载最新版本。
    WWDC 2015, 苹果发布了swift 2,宣布了swift语言第二次重大修订,其中包括了几个新的语言特征,以帮助你提升编码方式。
    在这些的新特征中,最令人兴奋的是协议扩展。在swift第一个版本,你可以扩展已经存在的类,结构体,和枚举类型的功能。现在有了swift 2,你也可以扩展协议。
    可能刚开始你会觉得这是一个微不足道的特征,但是协议扩展真的能量巨大,且可以转变你的编码方式。在这篇turorial,你会探索创建,使用协议扩展的方法,还有新技术和面向协议编程模式(以下简称:POP)。
    你将会看到Swift团队是怎样使用协议扩展以提升swift标准库,和协议扩展怎样影响你的代码。

    开始

    开始在XCode中创建一个新的Playground, 选择File\New\Playground...,然后命名Playground为SwiftProtocols。你可以选择任何平台,因为这篇tutorial所有的代码跟平台无关。点击Next选择你要保存的路径,然后点击Create.
    Playground打开后,我们添加如下代码:

    protocol Bird {
         var name: String { get }
              var canFly:Bool { get }
         }
    
         protocol Flyable {
             var airspeedVelocity: Double { get }
         }
    }
    

    这里简单的定义了一个拥有2个属性name, 和canFly的Bird协议类型,还有一个Flyable类型,它定义了airspeedVelocity属性。
    在pre-protocol时期,你可能会把Flyable当做基类,然后依赖继承去定义Bird还有其他能fly的东西,例如:灰机。请记住:一切始于协议!

    定义遵守协议的类型

    在Playground下方添加struct的定义:

    struct FlappyBird: Bird, Flyable {    
      let name: String       
      let flappyAmplitude: Double   
      let flappyFrequency: Double    
      let canFly = true      
      var airspeedVelocity: Double {          
          return 3 * flappyFrequency * flappyAmplitude  
      }
    }
    

    这里定义了一个新的结构体FlappyBird, 它遵守Bird和Flyable协议,airspeedVelocity为计算属性。canFly属性设置为true。
    接下来,在playground下面添加以下2个结构体的定义:

    struct Penguin: Bird {     
       let name: String     
       let canFly: Bool
    }
    
    struct SwiftBird: Bird, Flyable {  
         var name: String { 
            return "swift \(version)" 
         }    
         let version: Double      
         let canFly = true     
         var airspeedVelocity: Double { 
              return 2000.0
         }
    }
    

    企鹅是鸟类,但不会飞。哈--- 还好你没有使用继承,让鸟类都能飞。海燕当然有非常快的飞行速度。
    你可能已经看到一些冗余。尽管在你的结构中已经有了Flayable的概念,每一种Bird类型还必须声明是否canFly。

    用默认实现扩展协议

    使用协议扩展,你可以为协议定义默认行为。在Bird协议下添加如下代码:

    extension Bird where Self: Flyable {     
       var canFly: Bool { return true }
    }
    

    这里定义Bird的一个扩展,当类型也为Flyable时,设置为canFly设置默认行为true。换句话说,任何一个遵守Flyable的Bird类型不必在显示声明。

    i wanna everything automatic

    swift 1.2 引入了where语法,之后swift 2使用where语法给协议扩展添加约束(Self作用是指,当Bird类型同时也遵守Flyable协议,canFly属性才能生效)
    现在从FlappyBird和SwiftBird结构体声明中删除let canFly = true。你会发现playground成功编译了,因为协议扩展帮你处理了协议的要求。

    为什么不用基类

    协议扩展和其默认实现好像跟其他语言的基类或者抽象类很相似,但是swift具有一下几个关键优势:

    • 因为类型可以遵守多个协议,可以拥有多个协议默认实现的功能。不像其他编程语言支持的类的多继承,协议扩展不会引入额外的状态。
    • 协议可以被类,结构体和枚举类型遵守。基类和继承受限于类类型(class类型)
      换句话说,协议扩展为值类型,而不仅仅是为类类型提供了定义默认行为的能力。
      你已经在结构体中见识过这种行为;接下来在playground添加枚举定义:
    enum UnladenSwallow: Bird, Flyable {
        case Africancase
        case Europeancase
        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!")}
        }
    }
    

    像其他值类型一样,遵守Bird和Flyable协议,你只需要定义协议要求的属性。因为它遵守这2个协议,所以UnladenSwallow的canFly属性也获得了默认实现。
    你真的认为该 tutorial只会包括airspeedVelocity属性,而不会包括Monty Python的引用。(哈,可扩展。。。)

    扩展协议

    可能协议扩展最普遍的用法就是扩展外部协议了,可能是定义在swift标准库的协议,也可能是定义在第三方框架的协议。在playground下面添加如下代码:

    extension CollectionType {
        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()
                i += 1
            }while ( index != self.endIndex)
            return result
        }
    }
    

    在CollectionType类型上扩展了一个新的方法skip(:), 它会遍历集合中的每一个元素,过滤掉不符合要求的元素(留下索引值能被参数skip整除对应的元素),然后返回一个过滤后的数组。
    CollectionType是一个被诸如swift中数组,字典等类型遵守的协议。这就意味着这个新的行为(skip(
    :) 方法)存在你APP中所有的CollectionType类型中。playground中添加如下代码,我们来见证这一行为:

    let bunchBirds: [Bird] = [
        UnladenSwallow.African,
        UnladenSwallow.European,
        UnladenSwallow.Unknown,
        Penguin(name: "King Penguin", canFly: false),
        SwiftBird(version: 2.0),
        FlappyBird(name: "Felipe", flappyAmplitude: 3.0, flappyFrequency: 20.0)
    ]
    bunchBirds.skip(3)
    

    这里,你定义了一个包含各种类型鸟类的数组,这些类型上文中你已经定义过。因为数组遵守CollectionType, 那么它就能用skip(_:)方法。

    扩展你自己的协议

    也许让你兴奋的是你可以像给swift标准库添加新的行为一样,你也可以给自己的协议定义新的行为。
    修改Bird协议声明,使之遵守BooleanType协议:

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

    遵守BooleanType协议意味着你的类型需要有一个Bool类型的 boolValue属性。那么是不是意味着你必须给现在或者之后的每一个Bird类型都添加这么个属性呢?
    当然,我们有更容易的办法———协议扩展。在Bird定义下添加如下代码:

    extension BooleanType where Self: Bird {
        var boolValue: Bool {return self.canFly}
    }
    

    这个扩展让canFly属性代表了每一个Bird类型的Boolean 值。playground中添加如下代码,一起来见证奇迹:

    if UnladenSwallow.African {
        print("I can fly!")
    }else {
        print("Guess I'll just sit here")
    }
    

    你会看到"I can fly!" 出现在辅助编辑器上。但是这里你仅是在if 语句中使用了UnladenSwallow。(你可以挖掘更多用法!!!)

    对swift标准库的影响

    你已经见证过了协议扩展是多么棒--- 你可以使用它自定义和扩展你自己的代码的能力,还有你APP之外的协议(标准库和第三方框架协议)。也许你会好奇,swift团队是怎么使用协议扩展来提升swift标准库的。
    swift通过向标准库中添加诸如map, reduce和filter等高阶函数来提升函数式编程范式。这些方法存在不同CollectionType类型中,例如:Array:

    ["frog", "pants"].map{ $0.length}.reduce(0){$0 + $1}
    

    array调用map会返回另一个array, 新的array调用reduce来归纳结果为9.(map会遍历数组中每一个元素,并且返回由数组长度组成的数组,reduce把新数组中元素长度做加运算)
    这种情况下,map和reduce作为swift标准库的一部分被包含在Array中,如果你按下cmd,点击map,你可以看到它怎么定义的:

    extension Array : _ArrayType {
             func map(transform: (T) -> U) -> [U]
     }
    

    这里的map函数被定义为Array的一个扩展. swift的函数范式工作范围远不止Array, 它在任意的CollectionType类型上都能工作。所以swift 1.2下是怎么工作的呢?
    按下cmd,点击Range的map函数,你将看到以下定义:

    extension Range {
          func map(transform: (T) -> U) -> [U]
    }
    

    这些表明在swift 1.2中, swift标准库中的各种CollectionType类型需要重新定义map函数。因为尽管Array和Range都是CollectionType,但是结构体不能被子类化,也没有统一的实现。
    这并不仅仅是swift标准库做出的细微差别,也对你使用swift类型做出约束。
    下面的泛型函数接受一个遵守Flyable协议的CollectionType类型参数,返回airspeedVelocity最大的元素。

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

    在swift 1.2中没有协议扩展,这会出现编译错误。map和reduce函数只会存在于预先定义的类型中,不会在任意的CollectionType都会工作。
    在swift 2.0中有了协议扩展,Array和Range的map定义如下:

    extension CollectionType {  
             func map(@noescape transform: (Self.Generator.Element) -> T) -> [T]
    }
    
    

    尽管你不能看到map源码---至少到swift 2开源(目前已经开源了),ColletionType有map的一个默认实现,而且所有的CollectionType都能免费获得🤗。
    在playground下面添加早先提到的泛型函数:

    func topSpeed(c: T) -> Double {   
         return c.map { $0.airspeedVelocity }.reduce(0) { max($0, $1) }
    }
    

    map和reduce函数可以用在你的Flyable实例集合中了。现在你终于可以回答他们中谁是最快的。

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

    结果应该无人质疑吧👹(海燕最快)

    延伸阅读

    你可以在这里下载本tutorial完整playground代码。
    你已经见识过POP的威力了,创建你自己的简单协议,然后使用协议扩展来扩展他们。有了默认实现,你可以给已经存在的协议一个统一的实现,有些像基类但是优于基类,因为它还可以应用自爱结构体和枚举类型上。
    另外,协议扩展不仅能够勇子啊你自定义的协议上,还可以扩展swift标准库,Cocoa, CocoaTouch中协议的默认行为。
    如果想要浏览swift 2.0令人兴奋的新特征,你可以阅读[our "what's new in swift 2.0 article"], 或者 Apple's swift blog
    你也可以在苹果开发者入口Protocal Oriented Programming观看WWDC Session深入理解背后的原理。

    相关文章

      网友评论

        本文标题:面向协议编程介绍(一切始于协议)

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