iT邦幫忙

2021 iThome 鐵人賽

DAY 6
0
Mobile Development

從零開始的8-bit迷宮探險!Swift SpriteKit 遊戲開發實戰系列 第 6

從零開始的8-bit迷宮探險【Level 6】Swift 基礎語法 (四)

今日目標

  • 認識類別 (class) 及繼承
  • 認識協定 (protocol)
  • 認識結構 (struct)

類別 (class)

類別是物件導向很重要的一個概念,在開發遊戲時也常用到它。
可以把它想像成一張角色設計圖,裡頭可能定義了角色會具備的屬性、能力。有了這張設計圖後,我們可以製作角色出來,也就是將角色實體化,變成一個真實的角色物件 (object)。

  • 接著來看宣告類別的方式:
    • 定義了一個角色類別 Role,具有頭髮顏色 hairColor 跟眼睛顏色 eyeColor 兩種屬性
    • 它有一個建構子 init,可帶入參數,並且在初始化時賦予 hairColoreyeColor
    • 定義了兩種方法,讓角色可以跑 run、跳 jump
class Role {
    var hairColor: String
    var eyeColor: String
    
    init(hairColor: String, eyeColor: String) {
        self.hairColor = hairColor
        self.eyeColor = eyeColor
    }
    
    func run() {
        print("run")
    }
    func jump() {
        print("jump")
    }
}
  • 接著可以創建一個實體:
    • 透過 Role(hairColor: "orange", eyeColor: "blue") 可以創建一個實體,並且帶入這個角色特有的參數。例如我們創建一個人類的角色實體 human
    • 透過 human.hairColor 取得實體的屬性
    • 透過 human.run() 可以執行方法
let human = Role(hairColor: "black", eyeColor: "brown")
print("hair color: \(human.hairColor)")
print("eye color: \(human.eyeColor)")
human.run()
human.jump()
印出結果:
hair color: black
eye color: brown
run
jump

繼承

提到類別就不能少了繼承的概念,當一個 A 類別繼承了 B類別,則 B類別稱為父類別,A類別稱為子類別。子類別可以繼承父類別的屬性及方法,可以減少重複的程式碼。
在 swift 裡,使用繼承的方式可以用 :,將要繼承的類別放在冒號後面,並且最多只能繼承一個類別
例如,宣告一個新的勇者類別 Brave,讓它繼承 Role 類別

  • 可以在方法前加上 override,覆寫原本的 run方法
  • 可以新宣告飛 fly 方法
class Brave: Role {
    override func run() {
        print("run faster")
    }
    func fly() {
        print("fly")
    }
}
  • 創建一個實體:
    • 勇者類別 Brave 依然可以使用父類別 Role 中的屬性及方法,並且多了自己專有的方法,除了跑、跳之外,還可以飛
let brave = Brave(hairColor: "orange", eyeColor: "blue")
print("hair color: \(brave.hairColor)")
print("eye color: \(brave.eyeColor)")
brave.run()
brave.jump()
brave.fly()
印出結果:
hair color: orange
eye color: blue
run faster
jump
fly

協定 (protocol)

協定 (protocol) 的概念,其實和 interface 的概念非常相像,是一個尚未實作的、抽象的概念
它可以跟類別一樣定義一些屬性及方法,但是並不會實作,也就是方法內沒有內容。

  • 宣告協定的方式:
    • 新增一個超能力 SuperPower 的協定,讓勇者來使用
    • 使用協定的人必須含有 useSuperPower() 方法的實作
protocol SuperPower {
    func useSuperPower()
}
  • Brave 類別遵循 SuperPower 協定:
    • 我們在繼承的 Role 類別後方,使用逗號 ,,再加上 SuperPower 協定。
    • 協定必需寫在類別的後方,可以同時遵循很多種協定。
    • 實作 useSuperPower() 方法的內容,讓勇者具有瞬間移動的超能力
class Brave: Role, SuperPower {
    override func run() {
        print("run faster")
    }
    func fly() {
        print("fly")
    }
    func useSuperPower() {
        print("move instantly!!")
    }
}
let brave = Brave(hairColor: "orange", eyeColor: "blue")
brave.useSuperPower()
印出結果:
move instantly!!
  • 協定的好處
    當有不同的東西都需要某一種方法,並且方法的內容也不一樣時,我認為滿適合使用協定的概念來實作。
    例如,我們再創建一個怪物的類別 Monster,同樣繼承 Role 類別。我們希望勇者跟怪物同樣都可以使用超能力,並且他們的超能力不同,因此我們將 Monster 也遵循 SuperPower 協定
class Monster: Role, SuperPower {
    func useSuperPower() {
        print("fireball!!")
    }
}
let monster = Monster(hairColor: "red", eyeColor: "red")
monster.useSuperPower()
印出結果:
fireball!!
  • 現在,我的勇者和怪物兩種角色,都可以使用超能力方法 useSuperPower() 了,並且具有不同種類的超能力。而最初創建的人類角色,沒有遵循超能力協定 SuperPower,因此也確實不能使用超能力。
  • 我們不需要將 useSuperPower() 方法宣告在 Role 類別中,也不需要在一開始就定義他的方法內容,只需要宣告在抽象的協定中,並且只在使用到的地方實作方法即可。
  • 加上了協定後,Xcode 會貼心地提醒必須實作的方法,否則會報錯,可以讓開發者更清楚知道需要實作哪些方法。

結構 (struct)

結構 (struct) 和類別 (class) 類似,可以宣告屬性及方法,但是結構不能繼承,不過它是可以使用協定的。

  • 宣告一個結構
    • 定義屬性及方法
    • 建構子 init 可以省略
    • 遵循 SuperPower 協定,並且實作 useSuperPower() 方法
struct Brave: SuperPower {
    var hairColor: String
    var eyeColor: String

    func fly() {
        print("fly")
    }
    func useSuperPower() {
        print("move")
    }
}
  • 創建一個實體:
let brave = Brave(hairColor: "orange", eyeColor: "blue")
print("hair color: \(brave.hairColor)")
print("eye color: \(brave.eyeColor)")
brave.fly()
brave.useSuperPower()
印出結果:
hair color: orange
eye color: blue
fly
move

結構 vs 類別

從上述的範例,結構看起來感覺跟類別很像對吧,但是他們還是有差異的。

  • 結構儲存的內容是實體的值 (value type)
  • 類別儲存的內容是實體存放的位址 (reference type)

簡單來講,當我對一個結構的實體做修改,我可以確實只修改到它。
而當我對一個類別的實體做修改,有可能會有其他實體也一起被修改!原因是當修改類別的實體時,實際上是對它指向的位址做修改,當有其他實體也是指向同個位址時,就會一起被修改了。

  • 以這個範例為例:
    當對 structBrave2 做修改時,structBrave 還是維持原本的值 (blue)
    當對 classBrave2 做修改時,classBrave 的值也變成新的值了 (green)
struct StructBrave {
    var hairColor: String
    var eyeColor: String
}
class ClassBrave {
    var hairColor: String
    var eyeColor: String
    
    init(hairColor: String, eyeColor: String) {
        self.hairColor = hairColor
        self.eyeColor = eyeColor
    }
}
let structBrave = StructBrave(hairColor: "orange", eyeColor: "blue")
let classBrave = ClassBrave(hairColor: "orange", eyeColor: "blue")
var structBrave2 = structBrave
var classBrave2 = classBrave
structBrave2.eyeColor = "green"
classBrave2.eyeColor = "green"
print(structBrave.eyeColor)
print(classBrave.eyeColor)
印出結果:
blue
green
  • 由於結構是 value type,因此如果使用 let 宣告實體,不能修改他的屬性
let structBrave = StructBrave(hairColor: "orange", eyeColor: "blue")
let classBrave = ClassBrave(hairColor: "orange", eyeColor: "blue")
structBrave.eyeColor = "green" // 報錯,不能修改常數
classBrave.eyeColor = "green" // 成功編譯,因 var eyeColor: String 為變數

以上是今日的介紹~
明天將進入 swift 基礎語法的最後一篇介紹,繼續加油吧!/images/emoticon/emoticon61.gif


上一篇
從零開始的8-bit迷宮探險【Level 5】Swift 基礎語法 (三)
下一篇
從零開始的8-bit迷宮探險【Level 7】Swift 基礎語法 (五)
系列文
從零開始的8-bit迷宮探險!Swift SpriteKit 遊戲開發實戰30

尚未有邦友留言

立即登入留言