繼承
繼承(inherit
)這個概念主要是類別(class
)在使用,是物件導向的一個重要特性。一個類別可以繼承另一個類別的屬性(property
)、方法(method
)及其他特性。
一個類別繼承其他類別時,這個類別會被稱為子類別(subclass
)。被繼承的類別則是稱為父類別(superclass
,直翻會是超類別,但還是以父類別為主,以與子類別相對應)。
基礎類別
基礎類別(base class
)就是不繼承於其它類別的類別。
Swift 中沒有一個通用的基礎類別,只要一個類別沒有繼承於其他類別,這個類別即為一個基礎類別。
以下先定義一個基礎類別,其實就是一個普通的類別:
// 定義一個遊戲角色職業通用的類別
class GameCharacter {
// 攻擊速度
var attackSpeed = 1.5
// 這個職業的敘述
var description: String {
return "職業敘述"
}
// 執行攻擊的動作
func attack() {
// 無任何動作 有待子類別實作
}
}
// 生成一個類別 GameCharacter 的實體
let oneChar = GameCharacter()
// 印出:職業敘述
print(oneChar.description)
上述程式為一個基礎的遊戲角色職業類別,僅是定義了一些通用的內容,接著需要再定義子類別來完備。
生成子類別
生成子類別(subclassing
)指的是基於一個基礎類別來定義一個新的類別,子類別會繼承父類別所有的特性,且還可以增加新的特性。
使用方式為在類別名稱後面加上冒號:
,接著寫上父類別名稱,如下:
class 子類別: 父類別 {
子類別的定義內容
}
以下是個例子,這邊定義一個繼承自類別GameCharacter
的新類別Archer
:
class Archer: GameCharacter {
// 新增一個屬性 攻擊範圍
var attackRange = 2.5
}
// 父類別所有的特性都一併繼承下來
let oneArcher = Archer()
// 可以直接以點語法來存取或設置一個父類別中定義過的屬性
oneArcher.attackSpeed = 1.8
一個子類別仍然可以再被其他類別繼承,以下再定義一個類別Hunter
,繼承自類別Archer
:
class Hunter: Archer {
// 新增一個方法 必殺技攻擊
func fatalBlow() {
print("施放必殺技攻擊!")
}
}
let oneHunter = Hunter()
// 這個類別一樣可以使用 Archer 及 GameCharacter 定義過的屬性及方法
print("攻擊速度為 \(oneHunter.attackSpeed)")
print("攻擊範圍為 \(oneHunter.attackRange)")
// 當然自己新增的方法也可以使用
oneHunter.fatalBlow()
覆寫
類別繼承的同時,子類別可以重新定義父類別中定義過的特性,如實體方法(instance method
)、型別方法(type method
)、實體屬性(instance property
)、型別屬性(type property
)或下標(subscript
),這種行為即是覆寫(overriding
)。
使用關鍵字override
來表示你要覆寫這個特性(即方法、屬性或下標)。
覆寫方法
以下為一個覆寫方法的例子:
// 使用並改寫前面定義的類別 Hunter
class OtherHunter: Archer {
// 覆寫父類別的實體方法
override func attack() {
print("攻擊!這是獵人的攻擊!")
}
// 省略其他內容
}
let otherHunter = OtherHunter()
otherHunter.attack()
// 即會印出覆寫後的內容:攻擊!這是獵人的攻擊!
覆寫屬性
覆寫屬性時,需要使用getter
(以及有時可省略的setter
)來覆寫繼承來的屬性,且一定要寫上屬性的名稱及型別,這樣才能確定是從哪一個屬性繼承而來的。
Hint
- 可以將一個繼承來的唯讀屬性覆寫為一個讀寫屬性,但不行將一個讀寫屬性覆寫為唯獨屬性。即原本有
setter
的話,覆寫時就一定要有setter
。
以下是一個例子:
// 使用並改寫前面定義的類別 Hunter
class AnotherHunter: Archer {
// 覆寫父類別的屬性 重新實作 getter 跟 setter
override var attackSpeed: Double {
get {
return 2.4
}
set {
print(newValue)
}
}
// 省略其他內容
}
覆寫屬性觀察器
覆寫屬性時,通常可以加上屬性觀察器(property observer
),但以下兩點要注意:
- 當繼承的屬性為常數儲存型屬性或唯讀計算型屬性時,不能加上屬性觀察器,因為這兩者的屬性無法再被設置,所以
willSet
跟didSet
對它們沒有意義。 - 覆寫時不能同時有
setter
跟屬性觀察器(willSet
跟didSet
),因為setter
中即可做到屬性觀察器的功能要求。
雖然說是覆寫,但如果覆寫的父類別屬性也有屬性觀察器,其實子類別跟父類別兩者的屬性觀察器都會被執行,例子如下:
// 使用並改寫前面定義的類別 Archer
class OtherArcher: GameCharacter {
// 覆寫一個屬性 重新實作 getter 跟 setter
override var attackSpeed: Double {
willSet {
print("OtherArcher willSet")
}
didSet {
print("OtherArcher didSet")
}
}
// 省略其他內容
}
// 使用並改寫前面定義的類別 Hunter
class SomeHunter: OtherArcher {
// 覆寫一個屬性 重新實作 getter 跟 setter
override var attackSpeed: Double {
willSet {
print("SomeHunter willSet")
}
didSet {
print("SomeHunter didSet")
}
}
// 省略其他內容
}
let someHunter = SomeHunter()
// 設置新的值 會觸發 willSet 跟 didSet
someHunter.attackSpeed = 1.8
// 依序會印出:
// SomeHunter willSet
// OtherArcher willSet
// OtherArcher didSet
// SomeHunter didSet
上述程式中可以知道,willSet
觸發時,會先執行子類別的再來才是父類別的,而didSet
則是相反,先執行父類別的再來才是子類別的。
存取父類別的屬性,方法及下標
在前面章節介紹類別的時候,提到類別有一個隱藏的內建屬性 self,可以在方法中代表類別本身。而當繼承自另一個類別時,可以使用super
屬性來存取父類別的屬性、方法或下標。
- 方法
someMethod()
的覆寫實作裡,使用super.someMethod()
來呼叫父類別的someMethod()
方法。 - 屬性
someProperty
的getter
及setter
覆寫實作裡,使用super.someProperty
來存取父類別的someProperty
屬性。 - 下標的覆寫實作裡,使用
super[someIndex]
來存取父類別的下標。
以下為一個例子:
// 使用並改寫前面定義的類別 Hunter
class GoodHunter: Archer {
// 覆寫一個屬性 並使用父類別原先的屬性值
override var description: String {
return super.description + " 精通箭術的獵人"
}
// 省略其他內容
}
let goodHunter = GoodHunter()
// 印出:職業敘述 精通箭術的獵人
print(goodHunter.description)
防止覆寫
可以在類別的方法、屬性或下標前面加上final
,來防止它們被覆寫,使用方式為final var
、final func
或是final class func
這樣。
甚至也可以在整個類別的關鍵字class
前面加上final
,這樣整個類別都不能再被繼承。
範例
本節範例程式碼放在 ch2/inheritance.playground