游諭 Swift Dev 🦄

物件導向程式設計對於新手是非常困難的事情,今天我們有許多程式語言是以物件導向來運作的,像是 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: .斧頭)
  1. 場景設定: 一個農場、一棟城堡、一片平原
class 場景 {
    let name:String
    var characterAtHere:[角色] = []
    func peopleComing(_ people:角色) {
    ...
    }
    func peopleLeaving(_ people:角色) {
    ...
    }
}

let 農場 = 場景(name: "快樂農場")
let 城堡 = 場景(name: "魔王城堡")
let 平原 = 場景(name: "普通平原")
  1. 故事: 農場裡的 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 的問題!

Tagged with: