介紹在Swift2面向協議編程(譯)

原文戳我
這個教程要求Xcode7和Swift2,在這里還是測試版,大家可以去下載最新的.
在wwdc2015,發布了Swift2,包含了新的特性來提高你寫代碼的方式.
最令人興奮的特性是協議拓展(protocol extensions) . 在swift1是可以拓展已存在功能性的class,struct,enum類型. 在swift2,你還可以拓展protocol.
盡管最初看起來像一個次要的功能, protocol extensions 確實非常有用,能夠改變你寫代碼的方式. 在這個教程,你會探索生成和使用protocol extensions的幾種方式以及新的技巧和面向協議編程的設計模式,它們會讓你的代碼更好.
你也能看到Swift team怎樣使用protocol extensions 來提高Swift的標準庫的,它是如何影響你寫代碼的方式.
開始啦
先創建playground,名字為swift-Procotols. 添加以下代碼:

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

這里定義了一個簡單的protocol Bird 和簡單的properties name和canFlyFlyable協議,它定義 airspeedVelocity變量.
在以前,你可能構建一個含有Flyable的基類,然后依賴于繼承來定義Bird或者其他能fly的東西,例如airplanes. 在這里請記住:任何東西都從一個協議開始!
當你下一步開始定義實例的時候,你會發現它是怎樣讓整個系統更加靈活的.
定義 Protocol-conforming 類型
添加一下結構體定義到剛剛的代碼下面:

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

這定義了一個新的結構體FlappyBird, 它遵循BirdFlyable protocols. 它的airspeedVelocity 是通過flappyFrequencyflappyAmplitude組成的一個函數來計算的.要flappy,就返回surecanFly.
下一步,在添加兩個結構體定義到剛才的代碼下面

structPenguin: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{return2000.0}
}

一個Penguin 是一個Bird,但是不能fly.這是一個你沒有利用繼承方式的一個好處. 畢竟不是所有的birds都能fly. 一個SwiftBird當然是很快的,有著一個很高的airspeed velocity.
你已經發現了還有一些冗余. 每種類型的Bird都必須聲明canFly屬性,盡管已經有一個Flyable概念在你的系統.
用默認實現來擴展協議extenting Protocols
有了protocol extensions, 你能定義一個協議的默認行為. 添加下面代碼到Bird協議定義:

extension Bird where Self:Flyable{
  // Flyable birds can fly!
  var  canFly:Bool{returntrue}
}

這定義了一個Bird protocol的擴展,它設置了canFlytrue的默認行為,不管類型是不是Flyable. 換言之,任何Flyablebird不再需要顯性聲明了!

protocols-extend.png

我不能總是定義我自己的協議,但當我定義了自己的協議,我會用默認行為來擴展它們.
Swift1.2 介紹了協議擴展是需通過if-let語法來實現的,而Swift2帶來了更好的條件擴展協議.
FlappyBirdSwiftBird結構體定義中,刪除let canFly = true. 你會發現playground成功編譯,因為protocol extension現在會處理你的要求了.
為什么不使用基類呢?
Protocol extensions 和默認實現可能看起來和使用基類或者說是其他語言中的抽象類, 但protocol extensions提供了一些在Swift關鍵的優勢:
因為types 可以遵循多個協議,它們可以設置多個協議的默認行為. 不像一些語言的類能夠實現多繼承,協議擴展不引入任何額外的狀態.
Protocol能夠被classes, structenums遵循. 基類和繼承被只能是class types.
換言之,Protocol extensions 讓我們可以為value types 定義默認行為,而不僅僅是class.
你已經看到這種做法應用于結構體, 下面, 添加下面的enum 定義到playground的最后.

enumUnladenSwallow:Bird, Flyable{
  caseAfrican
  caseEuropean
  caseUnknown
  varname:String{
    switchself{
      case.African:
        return"African"
      case.European:
        return"European"
      case.Unknown:
        return"What do you mean? African or European?"
    }
  }
   varairspeedVelocity:Double{
    switchself{
      case.African:
        return10.0
      case.European:
        return9.9
      case.Unknown:
        fatalError("You are thrown from the bridge of death!")
    }
  }
}

就是其他value type一樣, 你需要做的就是定義正確的properties, 例如UnladenSwallow 遵循2個協議. 因為它遵循了BirdFlyable協議, 所以它設置canFly默認實現.
擴展Protocols
通常最通用的協議擴展時候是擴展額外的協議,無論是在Swift標準庫還是第三方的framework.
添加下面代碼到playground:

extension CollectionType{
  func skip(skip:Int)->[Generator.Element]{
    guard skip != 0 else { return[] }
    var index = self.startIndex
    var result:[Generator.Element] = []
    vari = 0
    repeat{
      if i%skip == 0 {
        result.append(self[index])
      }
      index=index.successor()
      i++
    } while(index != self.endIndex)
    return result
  }
}

這定義了CollectionType的擴展,定義了一個新的skip(_:)方法,這個方法跳過collection里面每個skip元素,返回那些未被跳過的元素組成的一個數組.
CollectionType 是一個協議,被Swift中的arraysdictionariestype遵循. 這意味著新的行為存在于每個CollectionType在你的App里! 添加下面的代碼到playground最下面:

letbunchaBirds:[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(3)

這里,你是定義一個birdsarray,包含很多你已經定義了的類型. 因為arrays遵循了CollectionType.那意味著,它們有skip(_:)方法是可用.
擴展你們自己的協議
就像添加一個新的行為到來自Swift標準庫的protocols里面,你也可以定義默認的行為.
修改Bird Protocol,聲明遵循BooleanType protocol:

protocol Bird:BooleanType{

遵循BooleanType 意味你的type需要有BoolValue property 是它能像一個Boolean. 是否意味著你必須添加在這個property到每個Bird type?
當然不是,有一個更簡單的方式,就是協議拓展. 添加代碼到Bird定義下面:

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

這拓展使canFly property 代表 每個Bird type的Boolean value.
嘗試,添加下面代碼到playground:

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

你應該會看到”I can fly!” 被打印出來. 但尤其,你需要使用African Unladen Swallow 在一個條件語句.
對Swift標準庫的影響
你已經看到protocol extensions是一個很好的方式來自定義和擴展你代碼的能做的事情,以及協議定義在你的aoo之外. 你可能會驚訝,Swift 協議擴展,也用于提升標準庫的寫法.
Swift通過添加map,reduce,filter方法來提高函數編程的范例. 這些存在于不同的CollectionType成員,例如Array:

// 計算數組中字符個數
["frog","pants"].map{$0.length}.reduce(0){$0+$1}// 返回9

對一個數組調用map,返回另外一個數組. 而reduce被調用減少結果到最后value 9.
在這個例子中,mapreduce是包含在Array中,是Swift標準庫的一部分. 如果你cmd-clicked在map上,你會看到它是怎么定義的.
在Swift1.2 你會看到下面:

// Swift 1.2
extensionArray:_ArrayType{
  // Return an `Array` containing the results of calling
  // `transform(x)` on each element `x` of `self`

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

map函數在這里是定義的Array的一個拓展. Swift的函數式的函數使用不僅在Array上. 然而,而是在所有的CollectionType 上. 那Swift1.2是怎樣實現它的?
如果你在一個Range上調用map,以及Cmd-clicked 在map上,你會看到下面的定義:

// Swift 1.2
extension Range{
  // Return an array containing the results of calling
  // `transform(x)` on each element `x` of `self`.
  func map(transform:(T)-> U)->[U]
}

結果表明在Swift1.2,在Swift標準庫里,map需要被每個Collectiontype重定義. 這是因為盡管ArrayRange都是CollectionType 但是structs不能被子類化,所以不能有相同的實現.
這不是Swift標準庫如何被構建的細微差別,這是約束了你對Swift types 能做的事情.
下面的函數,傳入FlyableCollectionType 返回含有最高的airspeedVelocity的元素.

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

在Swift1.2 沒有protocol extensions,這實際上是一個構建失誤. mapreduce函數只存在于一組預定義types,并不適用于所有CollectionType.
有Swift2.0 和Protocol extensions , map的定義在ArrayRange如下:

//Swift 2.0
extension CollectionType{
// Return an `Array` containing the results of mapping `transform`
// over `self`.
///
// - Complexity: O(N).
  func map(@noescape transform:(Self.Generator.Element)-> T)->[T]
}

盡管你不能看到map的源碼. swift2會開源. CollectionTypemap現在有一個默認的實現,所有CollectionType都有效.
添加如下代碼:

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

mapreduce方法,`Flyable實例的collection都能使用了. 現在,添加下面代碼,你就能回答,它們之中誰最快的問題了.

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

如何進行下一步學習?
你已經看到了面向協議開發, 建立自己的簡單的協議,使用協議擴展來擴展它們. 用默認的實現,你可以給已存在的協議添加共有的或自動的行為,很像一個基類,但比基類更好,因為能應用到struct和enums.
另外,協議擴展不僅可以使用到擴展你自己的協議,也可以擴展和提供默認行為到Swift標準庫,Cocoa和Cocoa Touch的協議里.
你應該看看Swift2.0令人興奮的新特性.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內容