天天看點

面向協定程式設計

Swift語言由于先天的後發優勢吸收了很多好的程式設計概念和特性,這些特性在給開發者帶來新可能的同時也進一步促進了語言本身的進化。這也是經曆三次Swift從入門到重學也不離不棄的原因?。Swift語言的優勢和特性大抵如下:

  • 跨平台。Linux平台的服務端正在發力,架構也在快速發展中。
  • 開源給這門語言帶來了無限可能。
  • OOP、POP、函數式這些程式設計理念全支援

這篇文章将會對其中的POP概念進行簡單講解。POP概念的傳播起點大概就是在“喜新厭舊”的蘋果在2015年WWDC上鼓勵使用Value Type來替換Reference Type。但是我們需要明白一點:開發世界沒有銀彈。POP作為一個理念和特性有其适用場景并不能完全替代OOP,後者存在幾十年是有理由的。

值類型與引用類型

上面說了POP興起于Value Type被鼓勵推崇後,那麼我們就有必要了解兩者的差別。值類型和引用類型最根本的差別就在于複制後的兩者的表現。做個類比:值類型複制動作相當于克隆了自己,你和克隆的對象之間是互相獨立的;引用類型複制動作後兩者的關系相當于自己和影子。類比不是很貼切,但是不妨礙了解。下面用代碼來說明:

Class HumanClass {
    var name: String
    
    init(name: String) {
        self.name = name
    }
}

var classyHuman = HumanCalss(name: "Bob")
classyHuman.name //Bob

var newClassyHuman = classyHuman    //複制一份

newClassyHuman.name = "Bobby"
classyHuman.name        //"Bobby"
           

當我們改變newClassyHuman對象的name屬性後,類對象classyHuman也跟着發生了變化。

接下來看值類型:

struct HumanStruct {
    var name: String 
}

var humanStruct = HumanStruct(name: "Bob")
var newHumanStruct = humanStruct        //同樣複制

newHumanStruct.name = "Bobby"
humanStruct.name                                //"Bob"
           

拷貝對象的屬性改變并沒有影響到原對象。

OOP的缺陷

作為幾十年前的一種理念,OOP可以說已經在現代被用爛了,幾乎所有的程式設計語言都支援這一特性。Java、Ruby等語言的設計理念中幾乎将一切事物都看作對象,對象即中心、對象即真理。但是我們反過頭來看OOP,其實可以很清晰的發現一些固有的缺陷。

  1. 繼承機制要求你在開始之前就能設計好整個程式的架構、結構、事物間的連接配接關系。這要求開發者必須有很好的分類設計能力,因為結構天生對改動有抵抗特性。這也是為什麼OOP領域中所有程式員都對重構諱莫如深,有些架構到最後代碼量急劇膨脹變得難以維護進而失控。
  2. 繼承機制帶來的另一個問題就:子類中會存在無用的父類屬性和方法,而這些備援代碼給子類帶來的一定的風險,而且對于層級很深的代碼結構來說Bug修複将會成為難題。
  3. 對象的狀态不是我們的編碼的好友,相反是我們的敵人。對象固有的狀态在分享和傳遞過程中是很難追蹤調試的,尤其在并行程式編碼中問題就更加明顯。OOP所帶來的可變、不确定、複雜等特征完全與并行程式設計中倡導的小型化、核心化、高效化完全背離。而免費午餐時代已經結束、摩爾定律的吃力表現意味着接下來的世界是屬于多核、并行、并發程式設計的。

這裡我們順便看下Apple用OOP思想完成的UIKit架構:

面向協定程式設計

整體結構設計其實非常的清晰,但是如果讓你在Apple對這個架構進行維護你有多少的自信能夠出色完成任務?别說架構維護了,其實我們大部分開發者都僅僅停留在架構的語言應用層面并沒有很大的耐心去厘清這個結構清晰的OOP架構。

歡迎來到POP的世界

你應該能夠猜到了與OOP以引用類型Class為基礎不同,POP理念的核心是值類型Struct。OOP是一種類金字塔的架構來搭建世界,POP則是一個扁平、非嵌套的代碼世界。

"A protocol defines a blueprint of methods, properties… The protocol can then be adopted by a class, structure, or enumeration" - Apple

上面是Apple對協定的定義說明,其中最關鍵的字眼莫過于“blueprint"。其實Protocal類似于籃球教練,他告訴球員們如何執行戰術、如何赢得比賽,但是他本人可能并不知道如何完成一個漂亮的大風車扣籃。

初識POP

首先,我們為人類建立一個特征藍圖。

protocol Human {
    var name: String {get set }
    var  carrer: String {get set }
    func sayHi() 
}
           

上面的協定中,我們除了聲明一些人類共有的特質沒有做任何其他的事情。其中的{get set }僅僅隻是表面這些屬性是可讀寫的,下面我們讓運動員遵循該協定:

struct Athlete: Human {
    var name: String = "Kobe Bryant"
    var carrer:   String - "Basketball Player"
    
    func satHi() { print("Hi,I'm \(name)" ) }
}
           

一旦Struct遵循了Human協定,它就必須按照規則實作所有的協定屬性和方法。當然Xcode同學會提醒你協定中的那些是你需要必須實作的,是以你不必害怕遺忘了其中的部分内容。

協定的繼承

在Protocol的世界中,我們可以使用OOP中的繼承概念對協定進行繼承形成協定樹。需要注意的是這同樣會帶來OOP中的惡果,不宜濫用。這裡我們僅僅是為了更加全面的介紹POP中協定所包含的各種能力。

我們在Human協定基礎上實作SuperHuman的協定。

protocol SuperHuman: Human {
    var canFly: Bool { get set } }
    func punch()
}
           

如果現在有一個Struct遵循了SuperHuman協定,那麼除了SuperHuman協定中的内容,我們同時也要實作Human協定中的内容。

struct SuperSaiyan: SuperHuman {
    var name: String = "Goku"
     var race: String = "Asian"
     var canFly: Bool = true
    func sayHi() { print("Hi, I'm \(name)") }
    func punch() { print("Puuooookkk") } 
}
           

當然我們也可是同時遵循多個協定,這在某種程度上來說相當于C++語言中類的多繼承的變通版本。

struct SuperSaiyan: SuperHuman, ProtocolAnother { }
           

協定拓展

協定拓展(Protocol Extension)是協定使用過程中最強大的武器,下面我們直接上碼:

// 會說英語的動物
protocol SuperAnimal {
    func speakEnglish() 
}
           

接下來我們對其進行拓展:

extension SuperAnimal {
    func speakEnglish() {
        print("I speak English, pretty coo;, huh?")
    }
}
           

最後我們看看效果如何:

struct Donkey: SuperAnimal{

}

var ramon = Donkey()
ramon.speakEnglish()        //  "I speak English, pretty cool, huh?"
           

從上面的示範效果我們可以發現:通過協定拓展我們給協定的遵循者一些預設的方法、屬性的實作。

作為Type的Protocol

如果我告訴你可以在數組裡面同時儲存Struct和Class對象而不用進行任何形式的類型轉換,你會作何感想?

是不是覺得不可思議,這确實有點突破正常,但同時在Swift中也是真實存在的。

直接上碼:

protocol Fightable {
    func legKick() 
}

struct StructKangaroo: Fightable {
     func legKick() { print("Puuook") }
 }
 
 class ClassKangaroo: Fightable { 
     func legKick() {print("Pakkkk") } 
}

let structKang = StructKangaroo()
let classKang = ClassKangaroo()

var kangaroos: [Fightable] = [structKang, classKang]

for kang in kangaroos { 
    kang.legKick() 
}
// "Puuook"
// "Pakkkk"
           

我們将同時采用Fightable協定的Class、Struct對象儲存到了[Fightable]類型的數組中,并且可以按照正常的數組一樣進行操作,是不是很神奇。

結語

程式設計世界沒有銀彈,每一種理念都有其存在的價值。這篇文章中我簡單的介紹了POP的概念以及Protocol世界中的一些文法糖,真正的POP需要你自己在這些基礎知識和文法糖的上層去應用。