iOS開發:Swift面向協議程式設計初探
最近有時間,挑了幾個今年WWDC中比較感興趣的Session視訊來學習,今天就抽時間整理一下關於Swift 2.0中一個比較新的概念面向協議程式設計。
相關的Session視訊連結如下:
寫在前面
面向協議程式設計是什麼?
你可能聽過類似的概念:面向物件程式設計、函數語言程式設計、泛型程式設計,再加上蘋果今年新提出的面向協議程式設計,這些統統可以理解為是一種程式設計正規化。所謂程式設計正規化,是隱藏在程式語言背後的思想,代表著語言的作者想要用怎樣的方式去解決怎樣的問題。不同的程式設計正規化反應在現實世界中,就是不同的程式語言適用於不同的領域和環境,比如在面向物件程式設計思想中,開發者用物件來描述萬事萬物並試圖用物件來解決所有可能的問題。程式設計正規化都有其各自的偏好和使用限制,所以越來越多的現代程式語言開始支援多正規化,使語言自身更強壯也更具適用性。
更多程式設計正規化和相關概念請參看:維基百科:程式設計正規化
對Swift語言所採用的程式設計正規化感興趣的朋友可以參看這篇文章:多正規化程式語言-以 Swift 為例
面向協議程式設計長什麼樣子?
在詳細解釋面向協議程式設計之前,我們先簡單地概括一下面向協議程式設計長什麼樣子?它與我們熟悉的面向物件程式設計有什麼不一樣?
簡單來說,面向協議程式設計是在面向物件程式設計基礎上演變而來,將程式設計過程中遇到的資料型別的抽取(抽象)由使用基類進行抽取改為使用協議(Java語言中的介面)進行抽取。更簡單點舉個栗子來說,一個貓類、一個狗類,我們很容易想到抽取一個描述動物的基類,也會有人想到抽取一個動物通用的協議,那後者就可以被叫做面向協議程式設計了。什麼?就是這樣而已?蘋果官方那麼正式的稱Swift是一門支援面向協議程式設計的語言,難道就是這麼簡單的內容?當然不會,有過面向物件程式設計經驗的人都會清楚,協議的使用限制很多,並不能適用於大多數情況下資料型別的抽象。而在Swift語言中,協議被賦予了更多的功能和更廣闊的使用空間,在Swift 2.0中,更為協議增加了擴充套件功能,使其能夠勝任絕大多數情況下資料型別的抽象,所以蘋果開始聲稱Swift是一門支援面向協議程式設計的語言。
面向協議程式設計對比面向物件程式設計的好處在哪裡?它會對我們程式的設計造成哪些影響?我們會在下文中繼續分析。
寫在中間
離開面向物件我們失去了什麼?
首先,讓我們來看看面向物件程式設計為我們帶來的好處。絕大多數熟悉一種或幾種面向物件程式語言的開發者都能隨口說出幾條面向物件程式設計的優點,比如資料的封裝、資料訪問的控制、資料型別的抽象、程式碼的可讀性和可擴充套件性等。這意味著離開了面向物件程式設計我們也就失去了如此多的好處。
哦,天吶!不要這樣好嘛?
回頭仔細想想,這些好處只有面向物件程式設計才有嘛?蘋果給了我們另一種答案:It’s Type, not Classes,是抽象的型別帶給我們如此多的好處,並不是面向物件中的類,類只是抽象型別的一種方式。比如在Swift語言中,使用結構體和列舉也同樣能夠實現對型別的抽象、資料的封裝和訪問控制等,這些好處又都回來了。
那麼有沒有什麼是類能帶給我們,而結構體和列舉辦不到的呢?當然有,不然我們真的可以離開面向物件了。面向物件程式設計還有兩個非常重要的特性我們還沒有提到:繼承和多型。繼承和多型為我們帶來了豐富多彩的世界,想想我們Cocoa Touch中的框架,這才是我們所熟悉的面向物件程式設計,它使我們能夠輕易地解決所面對的問題,並使我們的程式碼具有高度的可定製和可重用性。
我們的世界終於好像正常了。
擁有面向物件我們又得到了什麼?
那麼,面向物件程式設計在帶給我們這麼多好處的同時,是否還附帶了其他一些特性呢?比如說:要花費的代價。
我們先來看出現的第一個問題,多數面嚮物件語言中的物件都是使用引用型別,在物件傳遞過程中只是將引用複製一份並指向原有的物件,這樣就會出現問題。比如下面程式碼所示的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class Book {
var name: String
var pages: Int
init(name: String, pages: Int) {
self.name = name
self.pages = pages
}
}
class Person {
var name: String
var book: Book
init(name: String, book: Book) {
self.name = name
self.book = book
}
}
let 圍城 = Book(name: "圍城" , pages: 888)
let 小明 = Person(name: "小明" , book: 圍城) // 小明有一本全新的《圍城》
let 小剛 = Person(name: "小剛" , book: 圍城) // 小剛也有一本全新的《圍城》
小明.book.pages = 88 // 小明淘氣把書弄壞了,只剩88頁了
print(小剛.book.pages) // 輸出結果:88 WTF! Where is my new book?
|
故事的結尾是:小剛因為弄壞書被媽媽打了~ 不對啊,小明哪去了?我也不知道~
相信大多數面向物件程式語言的開發者都明白這是引用傳遞的原因,通常我們的解決辦法也很簡單,每次賦值的時候都先拷貝一份再進行賦值。當我們嘗試在上述程式碼中加入copy方法時,卻發現在Swift中物件預設並沒有copy方法,這是因為Swift更推薦使用值型別變數而不是引用型別的變數。如果真的需要呼叫copy方法,你可以將Book類繼承自NSObject,但這樣的做法真的一點都不優雅,也不夠Swiftpyer。實際上我們的問題也可以採用如下的解決辦法: