Swift 中的面向協議編程:是否優於面向對象編程?

做者:Andrew Jaffee,原文連接,原文日期:2018/03/28 譯者:陽仔;校對:numbbbbbLision;定稿:Forelaxhtml

在本文中,咱們將深刻討論 Swift 4 中的面向協議編程。這是一個系列兩篇文章中的第二篇。若是你尚未讀過 前一篇介紹文章,請在繼續閱讀本文以前先閱讀前一篇。ios

在本文中,咱們將探討爲何 Swift 被認爲是一門「面向協議」的語言;對比面向協議編程(POP)和麪向對象編程(OOP);對比「值語義」和「引用語義」;討論 local reasoning;用協議實現代理模式;用協議代替類型;使用協議多態性;重審個人面向協議的實際代碼;最終討論爲何我沒有 100% 使用 POP 編程。git

關於 WWDC 連接的一點說明 在這一系列關於 POP 的兩篇文章中,我至少添加了三個 Apple Worldwide Developers Conference (WWDC) 視頻的連接。在 Safari 中點擊這些連接將直接跳轉至視頻中的具體小節(並每每會從該處開始播放視頻)。若是你使用的不是 Safari,則須要瀏覽視頻,手動跳轉至某一小節,或者查看該視頻的文字版本。github

Swift 爲何是「面向協議」的語言?

我在 前一篇 POP 介紹文章 中,提到 Apple 聲稱」從核心上說,Swift 是面向協議的」。相信我,確實是這樣的。爲何呢?在回答這個問題以前,讓咱們先來比較幾種編程語言。編程

咱們最好對其餘語言也有所瞭解,由於在某些狀況下這將會有用處,好比在須要將 C++ 庫連接到 iOS 應用中的時候。個人不少 iOS 和 OSX 的應用連接了 C++ 的庫,由於這些應用有 Windows 平臺的版本。這些年,我支持了許多「平臺無關」的應用。swift

OOP 語言早已支持了接口。接口和 Swift 中的協議很類似,但並非徹底同樣。數組

這些語言中的接口指定了遵循該接口的類和(或)結構體必須實現哪些方法和(或)屬性。我這裏使用了「和(或)」,是由於好比 C++ 中沒有接口的概念,而是使用抽象類。而且,一個 C++ struct 能夠繼承自一個類。C# 中的接口容許指定其中的屬性和方法,struct 能夠遵循接口。Objective-C 中稱「協議」而不是「接口」,協議也能夠指定要求實現的方法和屬性,但只有類能夠聲明遵循接口,struct 不能夠。緩存

這些接口和 Objective-C 中的協議,並無方法的具體實現。它們只是指定一些要求,做爲遵循該協議的類/結構體實現時的「藍圖」。安全

協議構成了 Swift 標準庫 的基礎。正如我在 第一篇文章 中所展現,協議是 POP 方法論和範式的關鍵所在。bash

Swift 中的協議有其餘語言都不支持的特色:協議擴展。如下摘自 Apple 官方描述:

協議能夠被擴展,來給遵循該協議的類型提供方法、初始化方法、下標、計算屬性的具體實現。這就能夠容許協議自身定義一些行爲,而不是由各個類型本身去實現,或是由一個全局方法來實現。 經過擴展,咱們能夠爲協議所要求的任何方法和計算屬性提供一個默認的實現。若是一個遵循該協議的類型爲某個方法或屬性提供了其本身的實現,那麼該實現將會替代協議擴展中的實現。

在上一篇文章中,你已經看到我是怎麼使用協議擴展的,在本文中你會再次看到。它們是使得 Swift POP 如此強大的祕訣。

在 Swift 出現以前,協議在 iOS 中就已經十分重要。還記得我對 iOS 開發人員多年來採用的 UITableViewDataSource 和 UITableViewDelegate 等協議的 討論 嗎?再想想你天天寫的 Swift 代碼吧。

用 Swift 編程的時候,不可能不利用 標準庫 中的協議。例如,Array (一個繼承了 10 個協議struct),Bool (一個繼承了 7 個協議struct),Comparable (一個 繼承自另外一個協議的協議,而且是不少其餘 Swift 類型的繼承先祖),以及 Equatable (一個 不少 Swift 協議和類型的繼承先祖)。

花一些時間閱覽 Swift 標準庫,跟隨連接查看全部類型、協議、操做符、全局變量、函數。必定要看幾乎全部頁面都會有的 「Inheritance」 一節,並點擊 「VIEW PROTOCOL HIERARCHY ->」 連接。你將會看到不少協議,協議的定義,以及協議繼承關係的圖表。

記住很重要的一點:大部分 iOS(以及 OSX)SDK 中的代碼都是以類繼承的層次結構實現的。我相信不少咱們使用的核心框架仍然是用 Objective-C(以及一些 C++ 和 C)編寫的,例如 FundationUIKit。拿 UIKit 中的 UIbutton 舉例。利用 Apple 官方文檔頁面中的「繼承自」連接,咱們能夠從葉節點 UIButton 一直沿着繼承鏈向上查找到根節點 NSObjectUIButtonUIControlUIViewUIResponderNSObject。能夠形象表示爲:

POP 和 OOP

OOP 的優勢已經被開發者們討論得不少了,因此在這裏我只想簡單列舉一下。若是想了解詳盡的內容,能夠參考 我寫的這篇有關 OOP 在 Swift 中的實現的具體介紹

注意:若是你讀這篇文章的時候還不瞭解 OOP,我建議你在考慮學習 POP 以前,先學習 OOP。

OOP 的優勢包括可重用性,繼承,可維護性,對複雜性的隱藏(封裝),抽象性,多態性,對一個類的屬性和方法的訪問權限控制。我這裏可能還有所遺漏,由於開發者們已經總結出太多 OOP 的優勢了。

簡單地說,OOP 和 POP 都擁有大部分上述的特色,主要的一點不一樣在於:類只能繼承自其它一個類,但協議能夠繼承自多個協議。

正是因爲 OOP 的繼承的特色,咱們在開發中最好把繼承關係限制爲單繼承。由於多繼承會使代碼很快變得一團亂。

然而,協議卻能夠繼承自一個或多個不一樣的協議。

爲何須要推進面向協議編程呢?當咱們創建起一個龐大的類層次結構的時候,許多的屬性和方法會被繼承。開發者更傾向於把一些通用功能增長到頂層的——主要是高層的父類中(而且會一直加下去)。中層和底層的類的職責會更加明確和具體。新的功能會被放到父類中,這常常會使得父類充滿了許多額外的,無關的職責,變得「被污染」或是「臃腫」。中層和底層的類也所以繼承了不少它們並不須要的功能。

這些有關 OOP 的擔心並不是成文的規定。一個優秀的開發者能夠躲避不少剛纔提到的陷阱。這須要時間、實踐和經驗。例如,開發者能夠這樣解決父類功能臃腫的問題:將其餘類的實例添加爲當前類的成員變量,而非繼承這些類(也就是使用組合代替繼承)。

在 Swift 中使用 POP 還有一個好處:不只僅是類,值類型也能夠遵循協議,好比 structenum。咱們在下面將會討論使用值類型的一些優勢。

但個人確對遵循多協議的作法有一些顧慮。咱們是否只是將代碼的複雜性和難度轉移成另外一種形式了呢?即,將 OOP 繼承中的「垂直」的複雜性轉移成了 POP 繼承中的「水平」的複雜性了呢?

將以前展現的 UIButton 的類繼承結構和 Swift 中的 Array 所遵循的協議進行對比:

圖像來源:swiftdoc.org/v3.1/type/A…

Local reasoning 對這兩種狀況都不適用,由於個體和關係太多了。

值語義 vs. 引用語義

正如我上一篇文章所提到的,Apple 正在大力推廣 POP 和值語義的相關概念(他們還正在推廣另外一個與 POP 相關的範式,下文會講到)。上一次,我向大家展現了代碼,此次依然會用代碼來明確展現「引用語義」和「值語義」的不一樣意義。請參閱我 上一週的文章 中的 ObjectThatFlies 協議,以及今天文章中的 ListFIFOLIFO 以及相關協議。

Apple 工程師 Alex 說咱們 「應當使用值類型和協議來讓應用變得更好」。Apple sample playground 中,一節題爲「理解值類型」的代碼文檔這麼說:

標準庫中的序列和容器使用了值類型,這讓寫代碼變得更加容易。每個變量都有一個獨立的值,對這個值的引用並不是共享的。例如,當你向一個函數傳遞一個數組,這個函數並不會致使調用方對這個數組的拷貝忽然被改變。

這固然對全部使用值語義的數據類型都是適用的。我強烈建議你下載並完整閱覽整個 playground。

我並非要拋棄類這個使用引用語義的概念。我作不到。我本身已經寫了太多的基於類的代碼。我幫助個人客戶整理了數百萬行基於類的代碼。我贊成值類型通常來講比引用類型安全。當我寫新的代碼,或是重構已有代碼的時候,我會考慮在某些個案中積極嘗試。

引用語義下,類實例(引用)會致使 「意料以外的數據共享」。也有人稱之爲「意料以外的改變」。有一些 方法 能夠最小化引用語義的反作用,不過我仍是會愈來愈多地使用值語義。

值語義可以使變量避免受到沒法預計的更改,這實在很棒。由於「每一個變量有一個獨立的值,對這個值的引用是不共享的「,咱們可以避免這種沒法預計的更改致使的反作用。

由於 Swift 中的 struct 是一種值類型,而且可以遵循協議,蘋果也在大力推動 POP 以取代 OOP,在 面向協議和值編程 你能夠找到這背後的緣由。

Local reasoning

讓咱們探討一個很棒的主題,Apple 稱之爲 「Local reasoning」。這是由 Apple 一位叫 Alex 的工程師在 WWDC 2016 - Session 419,「UIKit 應用中的面向協議和值編程」中提出的。這也是 Apple 與 POP 同時大力推進的概念。

我認爲這不是個新鮮的概念。許多年之前,教授、同事、導師、開發者們都在討論這些:永遠不要寫高度超過一個屏幕的函數(即不長於一頁,或許更短);將大的函數拆解成若干小的函數;將大的代碼文件拆解成若干小的代碼文件;使用有意義的變量名;在寫代碼以前花點時間去設計代碼;保持空格和縮進風格的一致性;將相關的屬性和行爲整合成類和/或結構體;將相關的類和/或結構體整合進框架或庫中。但 Apple 在解釋 POP 的時候,正式提出了這個概念。Alex 告訴咱們:

Local reasoning 意味着,當你看你面前的代碼的時候,你不須要去思考,剩下的代碼怎樣去和這個函數進行交互。也許你以前已經有過這種感受。例如,當你剛加入一個新的團隊,有大量的代碼要去看,同時上下文的信息也很是匱乏,你能明白某一個函數的做用嗎?作到 Local reasoning 的能力很重要,由於這可以使得維護代碼、編寫代碼、測試代碼變得更加容易。

哈哈,你曾經有過這種感受嗎?我曾經讀過一些其餘人寫的真的很好的代碼。我也曾經寫過一些易讀性很是好的代碼。說實話,在 30 年的工做經驗中,我要去支持和升級的絕大部分現存的代碼都不會讓我感覺到 Alex 所描述的這種感受。相反,我常常會變得很是困惑,由於當我看一段代碼的時候,我每每對這段代碼的做用毫無頭緒。

Swift 語言的源代碼是開源的。請快速瀏覽一遍 下列函數,也不要花上三個小時去試圖理解它:

public mutating func next() -> Any? {
    if index + 1 > count {
        index = 0
	// 確保沒有 self 的成員變量被捕獲
        let enumeratedObject = enumerable
        var localState = state
        var localObjects = objects
        
        (count, useObjectsBuffer) = withUnsafeMutablePointer(to: &localObjects) {
            let buffer = AutoreleasingUnsafeMutablePointer<AnyObject?>($0)
            return withUnsafeMutablePointer(to: &localState) { (statePtr: UnsafeMutablePointer<NSFastEnumerationState>) -> (Int, Bool) in
                let result = enumeratedObject.countByEnumerating(with: statePtr, objects: buffer, count: 16)
                if statePtr.pointee.itemsPtr == buffer {
		    // 大多數 cocoa 類會返回它們本身的內部指針緩存,不使用默認的路徑獲取值。也有例外的狀況,好比 NSDictionary 和 NSSet。
		    return (result, true)
                } else {
                    // 這裏是一般情形,好比 NSArray。
		    return (result, false)
                }
            }
        }
        
        state = localState // 重置 state 的值
        objects = localObjects // 將對象指針拷貝回 self 
        
        if count == 0 { return nil }
    }
    defer { index += 1 }
    if !useObjectsBuffer {
        return state.itemsPtr![index]
    } else {
        switch index {
        case 0: return objects.0!.takeUnretainedValue()
        case 1: return objects.1!.takeUnretainedValue()
        case 2: return objects.2!.takeUnretainedValue()
        case 3: return objects.3!.takeUnretainedValue()
        case 4: return objects.4!.takeUnretainedValue()
        case 5: return objects.5!.takeUnretainedValue()
        case 6: return objects.6!.takeUnretainedValue()
        case 7: return objects.7!.takeUnretainedValue()
        case 8: return objects.8!.takeUnretainedValue()
        case 9: return objects.9!.takeUnretainedValue()
        case 10: return objects.10!.takeUnretainedValue()
        case 11: return objects.11!.takeUnretainedValue()
        case 12: return objects.12!.takeUnretainedValue()
        case 13: return objects.13!.takeUnretainedValue()
        case 14: return objects.14!.takeUnretainedValue()
        case 15: return objects.15!.takeUnretainedValue()
        default: fatalError("Access beyond storage buffer")
        }
    }
}
複製代碼

在你瀏覽過一遍以後,說實話,你能理解這段代碼嗎?我並無。我不得不花些時間多讀幾遍,並查閱函數定義之類的代碼。以個人經驗,這種代碼是廣泛存在的,而且不可避免須要常常修補的。

如今,讓咱們考慮理解一種 Swift 類型(不是一個函數)。查看 Swift 中的 Array定義。個人天,它繼承了 10 個協議:

  • BidirectionalCollection
  • Collection
  • CustomDebugStringConvertible
  • CustomReflectable
  • CustomStringConvertible
  • ExpressibleByArrayLiteral
  • MutableCollection
  • RandomAccessCollection
  • RangeReplaceableCollection
  • Sequence

點擊下方的「VIEW PROTOCOL HIERARCHY ->」連接按鈕——天哪,看這一坨麪條同樣的線條

若是你是在開發一個新項目,而且整個團隊可以遵循一套最佳開發指導方案的話,要作到 Local reasoning 會容易不少。少許代碼的重構也是作到 local reasoning 的較好的機會。對我來講,像其餘大部分事情同樣,代碼的重構須要慎重和仔細,要作到適度。

牢記:你幾乎一直要面對很是複雜的業務邏輯,這些邏輯若是寫成代碼,而且要讓一個團隊新人流暢讀懂,須要他/她接收一些業務知識的訓練和指導。他/她極可能須要查找一些函數、類、結構體、枚舉值、變量的定義。

代理和協議

代理模式是 iOS 中普遍使用的模式,其中一個必需的組成部分就是協議。在這裏咱們不須要再去重複。你能夠閱讀我有關該主題的 AppCoda 博客

協議類型以及協議多態性

在這些主題上我不許備花太多時間。我已經講過不少有關協議的知識,並向你展現了大量代碼。做爲任務,我想讓你本身研究一下,Swift 協議類型(就像在代理中同樣)的重要性,它們能給咱們帶來的靈活性,以及它們所展現的多態性。

協議類型 在我 關於代理的文章 中,我定義了一個屬性:

var delegate: LogoDownloaderDelegate?
複製代碼

其中 LogoDownloaderDelegate 是一個協議。而後,我調用了這個協議的一個方法。

協議多態性 正如在面向對象中同樣,咱們能夠經過遵循父協議的數據類型,來與多種遵循同一個協議族的子協議的數據類型進行交互。用代碼舉例來講明:

protocol Top {
    var protocolName: String { get }
}

protocol Middle: Top {

}

protocol Bottom: Middle {

}

struct TopStruct : Top {
    var protocolName: String = "TopStruct"
}

struct MiddleStruct : Middle {
    var protocolName: String = "MiddleStruct"
}

struct BottomStruct : Bottom {
    var protocolName: String = "BottomStruct"
}

let top = TopStruct()
let middle = MiddleStruct()
let bottom = BottomStruct()

var topStruct: Top
topStruct = bottom
print("\(topStruct)\n")
// 輸出 "BottomStruct(protocolName: "BottomStruct")"

topStruct = middle
print("\(topStruct)\n")
// 輸出 "MiddleStruct(protocolName: "MiddleStruct")"

topStruct = top
print("\(topStruct)\n")
// 輸出 "TopStruct(protocolName: "TopStruct")"

let protocolStructs:[Top] = [top,middle,bottom]

for protocolStruct in protocolStructs {
    print("\(protocolStruct)\n")
}
複製代碼

若是你運行一下 Playground 中的代碼,如下是終端的輸出結果:

BottomStruct(protocolName: "BottomStruct")

MiddleStruct(protocolName: "MiddleStruct")

TopStruct(protocolName: "TopStruct")

TopStruct(protocolName: "TopStruct")

MiddleStruct(protocolName: "MiddleStruct")

BottomStruct(protocolName: "BottomStruct")
複製代碼

真實的 UIKit 應用中的協議

如今,讓咱們來看一些實質性的東西,寫一些 Swift 4 的代碼——這些代碼是在我本身的應用中真實使用的。這些代碼應當能使你開始思考用協議來構建和/或拓展你的代碼。這也就是我在這兩篇文章中一直在描述的,「面向協議編程」,或者 POP。

我選擇向你展現如何去擴展或者說是延伸(隨便哪一種說法)UIKit 的類,由於 1) 你極可能很是習慣使用它們 2) 擴展 iOS SDK 中的類,好比 UIView,是比用你本身的類更加困難一些的。

全部 UIView 的擴展代碼都是用 Xcode 9 工程中的 Single View App 模板寫的。

我使用默認協議擴展來對 UIView 進行擴展——這麼作的關鍵是一種 Apple 稱之爲 「條件遵循」 的作法(也能夠看 這裏)。由於我只想對 UIView 這個類進行擴展,咱們可讓編譯器來把這個變成一項強制要求。

我常用 UIView 做爲一個容器來組織屏幕上的其餘 UI 元素。也有時候,我會用這些容器視圖來更好地查看、感受、排布個人 UI 視圖。

這裏是一張 GIF 圖片,展現了使用下面建立的三個協議來自定義 UIView 的外觀的結果:

注意,這裏我也遵照了 」Local reasoning「 的原則。我每個基於協議的函數都控制在一屏幕以內。我但願你能閱讀每個函數,由於它們並無太多代碼量,但卻頗有效。

爲 UIView 添加一個默認的邊框

假設我但願得到不少擁有相同邊框的 UIView 實例——例如在一個支持顏色主題的應用中那樣。一個這樣的例子就是上面那張圖片中,最上面那個綠色的視圖。

protocol SimpleViewWithBorder {}

// 安全的:"addBorder" 方法只會被添加到 UIView 的實例。
extension SimpleViewWithBorder where Self : UIView {
    func addBorder() -> Void {
        layer.borderColor = UIColor.green.cgColor
        layer.borderWidth = 10.0
    }
}

class SimpleUIViewWithBorder : UIView, SimpleViewWithBorder {
}
複製代碼

要建立、配置、顯示一個 SimpleUIViewWithBorder 的實例,我在個人 ViewController 子類中的 IBAction 中寫了以下代碼:

@IBAction func addViewButtonTapped(_ sender: Any) {
    let customFrame0 = CGRect(x: 110, y: 100, width: 100, height: 100)
    let customView0 = SimpleUIViewWithBorder(frame: customFrame0)
    customView0.addBorder()
    self.view.addSubview(customView0)
複製代碼

我不須要爲這個 UIView 的子類去建立一個特殊的初始化方法。

爲 UIView 添加一個默認的背景色

假設我但願不少 UIView 的實例都有相同的背景色。一個這樣的例子是上圖中,中間的藍色視圖。注意,我向可配置的 UIView 又更進了一步。

protocol ViewWithBackground {
    var customBackgroundColor: UIColor { get }
}

extension ViewWithBackground where Self : UIView {
    func addBackgroundColor() -> Void {
        backgroundColor = customBackgroundColor
    }
}

class UIViewWithBackground : UIView, ViewWithBackground {
    let customBackgroundColor: UIColor = .blue
}
複製代碼

要建立、配置、展現一個 UIViewWithBackground 的實例,我在個人 ViewController 子類中的 IBAction 中寫了以下代碼:

let customFrame1 = CGRect(x: 110, y: 210, width: 100, height: 100)
let customView1 = UIViewWithBackground(frame: customFrame1)
customView1.addBackgroundColor()
self.view.addSubview(customView1)
複製代碼

我不須要爲這個 UIView 的子類去建立一個特殊的初始化方法。

爲 UIView 添加一個可配置的邊框顏色

如今,我但願可以配置 UIView 邊框的顏色和寬度。用下列實現代碼,我能夠隨意建立不一樣邊框顏色、寬度的視圖。這樣的一個例子是上圖中,最下面的紅色視圖。向個人協議中去添加可配置的屬性有一點代價,我須要可以初始化這些屬性,所以,我爲個人協議添加了一個 init 方法。這意味着,我也能夠調用 UIView 的初始化方法。讀完代碼,你就會明白:

protocol ViewWithBorder {
    var borderColor: UIColor { get }
    var borderThickness: CGFloat { get }
    init(borderColor: UIColor, borderThickness: CGFloat, frame: CGRect)
}

extension ViewWithBorder where Self : UIView {
    func addBorder() -> Void {
        layer.borderColor = borderColor.cgColor
        layer.borderWidth = borderThickness
    }
}

class UIViewWithBorder : UIView, ViewWithBorder {
    let borderColor: UIColor
    let borderThickness: CGFloat

    // UIView 的必要初始化方法
    required init(borderColor: UIColor, borderThickness: CGFloat, frame: CGRect) {
        self.borderColor = borderColor
        self.borderThickness = borderThickness
        super.init(frame: frame)
    }

    // UIView 的必要初始化方法
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
複製代碼

要建立、配置、顯示一個 UIViewWithBorder 的實例,我在個人 ViewController 子類中的 IBAction 中寫了以下代碼:

let customFrame2 = CGRect(x: 110, y: 320, width: 100, height: 100)
    let customView2 = UIViewWithBorder(borderColor: .red, borderThickness: 10.0, frame: customFrame2)
    customView2.addBorder()
    self.view.addSubview(customView2)
複製代碼

我不想作的事

我不想去建立像這樣的代碼:

extension UIView {
    func addBorder() {  ...  }
    func addBackgroundColor() {  ...  }
}
複製代碼

這樣也許在一些狀況下是有效的,但我感受這種實現太粗泛了,容易喪失不少細顆粒度的控制。這種實現也容易使得這種構造方法變成 UIView 相關擴展方法的垃圾場,換句話說,代碼容易變得臃腫。隨着方法愈來愈多,代碼也變得愈來愈難以閱讀和維護。

在上述全部基於 UIKit 的協議中,我都使用了 UIView 的子類——引用類型。子類化可以讓我能直接訪問父類 UIView 中的任何內容,讓個人代碼清晰、簡短、易讀。若是我使用的是 struct,個人代碼會變得更加冗長,至於爲何,留給大家當作練習。

我作的事情

時刻記住,全部這些默認協議 extensions 能夠在類擴展中覆蓋。用一個例子和圖片來解釋:

protocol SimpleViewWithBorder {}

extension SimpleViewWithBorder where Self : UIView {
    func addBorder() -> Void {
        layer.borderColor = UIColor.green.cgColor
        layer.borderWidth = 10.0
    }
}

class SimpleUIViewWithBorder : UIView, SimpleViewWithBorder {
    // 覆蓋 extension 中的默認實現
    func addBorder() -> Void {
        layer.borderColor = UIColor.darkGray.cgColor
        layer.borderWidth = 20.0
    }
}
複製代碼

注意我在 SimpleUIViewWithBorder 中的註釋。看下圖中最上面的視圖:

真實的,基於協議的泛型數據結構

我很是驕傲在我本身的應用中,我可以寫儘可能少的 POP 代碼,來建立完整功能的泛型的棧和隊列的數據結構。想了解有關 Swift 中的泛型,請閱讀我 AppCoda 中的 文章

請注意,我使用協議繼承來幫助我利用抽象的 List 協議去建立更加具體的 FIFOLIFO 協議。而後,我利用協議擴展來實現 QueueStack 值類型。你能夠在下面的 Xcode 9 playground 中看到這些 struct 的實例。

我想向你展現的是如何像 Apple 建議的同樣,經過其餘協議來實現本身自定義的協議,所以,我建立了 ListSubscriptListPrintForwardsListPrintBackwardsListCount協議。它們如今還很簡單,但在一個實際的應用中將會展示出其做用。

這種繼承多個其餘協議的作法可讓開發者爲現有代碼增長新的功能,並且不會由於太多額外不相關的功能對代碼形成」污染「或」臃腫「。這些協議中,每個都是獨立的。若是是做爲類被添加到繼承層級中葉級以上的話,根據它們所處的位置,這些功能將會至少自動被其餘一些類繼承。

關於 POP,我已經講了足夠多來幫助你閱讀和理解代碼。再給出一個我是如何讓個人數據結構支持泛型的提示:關聯類型的定義

當定義一個協議的時候,有時能夠聲明一個或多個關聯類型,做爲協議定義的一部分。一個關聯類型提供了一個佔位名,用來表示協議中的一種類型。這個關聯類型真正的數據類型直到該協議被使用的時候才肯定。使用 associatedtype 關鍵字來指明一個關聯類型。

代碼以下:

protocol ListSubscript {
    associatedtype AnyType
    
    var elements : [AnyType] { get }
}

extension ListSubscript {
    subscript(i: Int) -> Any {
        return elements[i]
    }
}

protocol ListPrintForwards {
    associatedtype AnyType

    var elements : [AnyType] { get }
}

extension ListPrintForwards {
    func showList() {
        if elements.count > 0 {
            var line = ""
            var index = 1

                        for element in elements {
                line += "\(element) "
                index += 1
            }
            print("\(line)\n")
        } else {
            print("EMPTY\n")
        }
    }
}

protocol ListPrintBackward {
    associatedtype AnyType

    var elements : [AnyType] { get }
}

extension ListPrintBackwards {
    func showList() {
        if elements.count > 0 {
            var line = ""
            var index = 1

            for element in elements.reversed() {
                line += "\(element) "
                index += 1
            }
            print("\(line)\n")
        } else {
            print("EMPTY\n")
        }
    }
}

protocol ListCount {
    associatedtype AnyType

    var elements : [AnyType] { get }
}

extension ListCount {
    func count() -> Int {
        return elements.count
    }
}

protocol List {
    associatedtype AnyType

    var elements : [AnyType] { get set }

    mutating func remove() -> AnyType

    mutating func add(_ element: AnyType)
}

extension List {
    mutating func add(_ element: AnyType) {
        elements.append(element)
    }
}

protocol FIFO : List, ListCount, ListPrintForwards, ListSubscript {

}

extension FIFO {
    mutating func remove() -> AnyType {
        if elements.count > 0 {
            return elements.removeFirst()
        } else {
            return "******EMPTY******" as! AnyType
        }
    }
}

struct Queue<AnyType>: FIFO {
    var elements: [AnyType] = []
}

var queue = Queue<Any>()
queue.add("Bob")
queue.showList()
queue.add(1)
queue.showList()
queue.add(3.0)
_ = queue[0] // 該下標輸出 "Bob"
_ = queue.count()
queue.showList()
queue.remove()
queue.showList()
queue.remove()
queue.showList()
queue.remove()
queue.showList()
_ = queue.count()

protocol LIFO : List, ListCount, ListPrintBackwards, ListSubscript {
}

extension LIFO {
    mutating func remove() -> AnyType {
        if elements.count > 0 {
            return elements.removeLast()
        } else {
            return "******EMPTY******" as! AnyType
        }
    }    
}

struct Stack<AnyType>: LIFO {
    var elements: [AnyType] = []
}

var stack = Stack<Any>()
stack.add("Bob")
stack.showList()
stack.add(1)
stack.showList()
stack.add(3.0)
_ = stack[0] // 該下標輸出 3
_ = stack.count()
stack.showList()
stack.remove()
stack.showList()
stack.remove()
stack.showList()
stack.remove()
stack.showList()
_ = stack.count()
複製代碼

這一段代碼片斷在控制檯輸出以下:

Bob

Bob 1

Bob 1 3.0

1 3.0

3.0

EMPTY

Bob

1 Bob

3.0 1 Bob

1 Bob

Bob

EMPTY
複製代碼

我沒有 100% 使用 POP

在 WWDC 有關 POP 的視頻之一中,一位工程師/講師說 」在 Swift 中咱們有一種說法,不要從一個類開始寫代碼,從一個協議開始「。嘛~也許吧。這傢伙開始了有關如何使用協議來寫一個二分查找的冗長的討論。我有點懷疑,這是否是我許多讀者印象最深的部分。看完你失眠了嗎?

這有點像是爲了尋找一個 POP 解決方案而人爲設計出的一個問題。也許問題是實際的,也許這種解決方案有優勢,我也不知道。個人時間很寶貴,沒有時間浪費在這種象牙塔理論上。若是讀懂一段代碼須要超過 5 分鐘的時間,我就以爲這段代碼違背了 Apple 的 」local reasoning「 原則。

若是你和我同樣也是一個軟件開發者,最好始終對新的方法論保持一個開放的心態,而且始終將控制複雜度做爲你的主要工做重心。我毫不反對賺錢,但看得更高更遠一點是有好處的。記住,Apple 是一家公司,一家大公司,主要使命是賺大錢,上週五的市值已經接近 8370 億美圓,擁有數千億的現金和現金等價物。他們想讓每一個人都使用 Swift,而這些公司吸引人到自家生態系統的方法之一就是提供別人都提供不了的產品和服務。是的,Swift 是開源的,但 Apple 從 App Store 賺了大錢,所以應用正是讓全部 Apple 設備變得有用的關鍵,許許多多的開發者正在向 Swift 遷移。

我以爲沒有任何理由只用 POP 進行編程。我認爲 POP 和我使用的其餘許多技術,甚至是 OOP 同樣,都有一些問題。咱們是在對現實建模,或者至少說,咱們是在對現實進行擬合。沒有完美的解決方案。因此,將 POP 做爲你的開發工具箱中的一種吧,就像人們終年以來總結出的其餘優秀的方案同樣。

結論

30 年的開發經驗,讓我可以平心靜氣地說,**你應該瞭解協議和 POP。**開始設計並書寫你本身的 POP 代碼吧。

我已經花費了很多時間試用 POP,而且已經將這篇文章中的協議使用在了我本身的應用中,好比 SimpleViewWithBorderViewWithBackgroundViewWithBorderListFIFOLIFO。POP 威力無窮。

正如我在前一篇文章中提到的,學習並接受一種新方法,好比 POP,並非一個非對即錯的事情。POP 和 OOP 不只能並存,還可以互相補充。

因此,開始試驗、練習、學習吧。最後,盡情享受生活和工做吧。

本文由 SwiftGG 翻譯組翻譯,已經得到做者翻譯受權,最新文章請訪問 swift.gg

相關文章
相關標籤/搜索