物件導向程式設計對於新手是非常困難的事情,今天我們有許多程式語言是以物件導向來運作的,像是 C++, Objective-C, Python, Swift,作為 iOS 開發者,很少看到以 Swift 為語言的物件導向教學,今天我將以 Mit 6001 第 8 集: Object Oriented Progamming 作為基礎,以 Swift 的方式分享物件導向。
什麼是 Objects?
Object 是一種資料的抽象,這種抽象是包含了兩個事情:它的內部零件和它的運作指令。車子是一個 Object,這個 Object 內有許多零件,像是輪胎、座椅、動力系統。每個零件都可以再視為另一個 Object,像是輪胎可以是橡皮部分、輪圈、輪圈上的螺絲釘。而車子的運作指令就是轉動方向盤、油門剎車。我們可以說,零件是物件的 property,指令是物件的 interface。
所以我們可以用 Swift 的 class 來說明汽車這個 Object:
class 車子 {
var 動力系統: 動力系統種類
func 前進() {...}
func 後退() {...}
func 左轉() {...}
func 右轉() {...}
}
let miniCooper = 車子(動力系統: .渦輪增壓直列4缸)
let 動物園馬車 = 車子(動力系統: .成年馬(2))
這時候我們有了 2 個型別為車子的 Object,分別是 miniCooper 和 動物園馬車。
OBJECT ORIENTED PROGRAMMING (OOP) 的特性
OOP 可以將一連串的資訊與 function 約束在一起,就像是商店裡的大包裝一樣沒有辦法分別購買裡面的資訊。如果沒有這個功能,我們可能會這樣寫:
let miniCooper = 車子()
let miniCooper動力系統 = 渦輪增壓直列4缸
let 馬車 = 車子()
let 馬車動力系統 = 成年馬2匹
func miniCooper前進() {...}
func 馬車前進() {...}
func miniCooper後退() {...}
func 馬車後退() {...}
func miniCooper左轉() {...}
func 馬車左轉() {...}
func miniCooper右轉() {...}
func 馬車右轉() {...}
相較於 OOP 的寫法,是不是多出了許多呢!為什麼會有這麼大的差異,最明顯的就是 OOP 可以使程式碼有很大程度的重複使用。當我想到重複使用,我想到環保餐具,我們使用免洗筷雖然可以有一時的方便,但是隨著用的次數變多,我們漸漸受到濫用的懲罰;程式碼也是一樣的道理,一旦我們濫用了複製貼上,隨著程式碼的複雜會逐漸出現可大可小的懲罰。
學好 OOP 的重要心法:抽象
設計 OOP 是一門藝術,就像是寫劇本一樣,你會知道在劇情發展下什麼是重要的,什麼是不重要的;而在物件導向裡面,可以用一些台詞來解釋物件之間的複雜關係。例如: 1. 人物設定:主角 Yu 有一把寶劍,大魔王 Swift 有一支斧頭
class 角色 {
let name: String
let weapon: Weapon
enum Weapon {
case 寶劍
case 斧頭
}
}
let YuTheHero = 角色(name: "Yu", weapon: .寶劍)
let SwiftTheBoss = 角色(name: "Swift", weapon: .斧頭)
- 場景設定: 一個農場、一棟城堡、一片平原
class 場景 {
let name:String
var characterAtHere:[角色] = []
func peopleComing(_ people:角色) {
...
}
func peopleLeaving(_ people:角色) {
...
}
}
let 農場 = 場景(name: "快樂農場")
let 城堡 = 場景(name: "魔王城堡")
let 平原 = 場景(name: "普通平原")
- 故事: 農場裡的 Yu 離開農場,經過平原到了城堡,將魔王趕出城堡。
/* 背景設定 */
農場.peopleComing(YuTheHero)
城堡.peopleComing(SwiftTheBoss)
/* 劇情發展 */
農場.peopleLeaving(YuTheHero)
平原.peopleComing(YuTheHero)
平原.peopleLeaving(YuTheHero)
城堡.peopleComing(YuTheHero)
城堡.peopleLeaving(SwiftTheBoss)
在這個例子中,我們將角色與場景抽象成為 Type,建立了 角色要有 name、weapon,場景要有 name 與 characterAtHere,這樣的設計讓我們可以產生 Yu 與 Swift 這兩個不同的角色,這些角色可以存在於不同的場景。你可能會覺得可以有不同的抽象方式,像是人物可以有Enter, leave 的 function,而不是場景;對於這個設計疑問是一件難以說明的事情,因為隨著需求(劇情)的演進,常常會有設計不適用的情形發生,所以在設計物件導向的時候需要經過仔細的思考與規劃。
開放部分與不開放部分 (Public/Private)
OOP 中有所謂的可視區分,我稱之 “開放部分與不開放部分”。開放(Public) 就是 Interface,不開放(Public)就是 SOP,最為有效的比喻就是機關的櫃檯: 在咖啡店的例子中,我們向咖啡店買一杯咖啡,咖啡店經過內部的一些標準製作流程,其中包含磨咖啡豆、煮水、泡咖啡、包裝咖啡,最後咖啡店將咖啡交給我們。在客人眼中,客人面對的是一間咖啡店,咖啡店接受的指令只有買咖啡。而在咖啡店內部的視角,咖啡店有咖啡原料、機器等 property,有一些指令像是磨咖啡豆、煮水、泡咖啡、包裝咖啡等等獨立的指令,這些指令不會讓客人逐一呼叫,咖啡店將這些指令設為 private
,相對的透過 public
提供一個 “買咖啡” 的使得客人可以取得一份咖啡。
class 咖啡店 {
// MARK: - Private scope
private func 磨咖啡豆() -> 咖啡粉 {...}
private func 煮水() -> 水 {...}
private func 泡咖啡(咖啡粉,水) {...}
private func 包裝咖啡(咖啡) {...}
}
// MARK: - Public scope
extension 咖啡店 {
public func 買咖啡() -> 咖啡產品 {
let 咖啡粉 = 磨咖啡豆()
let 水 = 煮水()
let 泡好的咖啡 = 泡咖啡(咖啡粉,水)
let 包裝好的咖啡 = 包裝咖啡(泡好的咖啡)
return 包裝好的咖啡
}
}
在這裡,我們可以說: 咖啡店將做一份咖啡這個商業機密封裝起來。
結論: 學會了抽象與封裝之後
OOP 有 4 大特性: 抽象、封裝、繼承、多型,我們學會了如何提取重複的抽象特性,學會了如何將步驟保護起來的封裝特性,接下來,你可以繼續學習 DRY 的技巧,繼承與多型。DRY 是 Don't Repeat Youself 的縮寫,我們已經知道可以用迴圈來做到重複的程式碼消除。
func 罰寫7次() {
print("我不應該重複我的程式碼")
print("我不應該重複我的程式碼")
print("我不應該重複我的程式碼")
print("我不應該重複我的程式碼")
print("我不應該重複我的程式碼")
print("我不應該重複我的程式碼")
print("我不應該重複我的程式碼")
}
如今,你學會了物件導向,你也不應該做這樣的事情:
class 小學生 {
func 罰寫n次(n: Int) {
for _ in 0..<n {
print("我不應該重複我的程式碼")
}
}
}
class 大學生 {
func 罰寫n次(n: Int) {
for _ in 0..<n {
print("我不應該重複我的程式碼")
}
}
}
而繼承與多型可以有效的處理這個 DRY 的問題!