Swift 程式語言 — Inheritance

讓我們來探討一下 Swift 中所謂的繼承。

Jeremy Xue
Jeremy Xue ‘s Blog
11 min readMay 26, 2019

--

Photo by Anton Darius | @theSollers on Unsplash

# 前言

Classes 可以繼承方法、屬性和其他特性。當一個 class 繼承另一個 class 時,繼承的 class 稱之為 subClass,被繼承的 class 稱為 superClass。繼承是一個基本的行為,他將 class 與 Swift 中的其他類型區隔開。

Swift 中的 Classes 可以調用和訪問屬於 subClass 的方法、屬性和下標,並且能夠提供這些方法、屬性、下標的自己的重寫版本(override),來優化或修改這些行為。Swift 透過檢查 override 定義是否具有匹配的 superClass 定義來幫助確認你的 override 是正確的。

Classes 還可以添加屬性觀察器到繼承的屬性,以便於在屬性值更改時得到通知。屬性觀察器可以添加到任何屬性,無論它最初是被定義為存儲屬性還是計算屬性。

# 定義 Base Class

任何不從其他 class 繼承的 class 都稱之為 base class。

Swift 類不會從一個通用 baseClass 繼承。你沒有指定特定 superClass 的 class 都會以 baseClass 的形式創建。

下方的範例定義了一個名為 Vehicle 的 baseClass。這個 class 定義了名為 currentSpeed 的存儲屬性,默認值為 0.0currentSpeed 屬性的值由名為 description 的唯讀計算屬性使用,用來創建車輛的描述。

Vehicle 還定義了一個名為 makeNoise 的方法。此方法實際上不對 Vehicle 實例進行任何操作,但稍後將會由 Vehicle 的 subClass 自定義:

使用初始化器語法來創建 Vehicle 實例,該語法寫為類型名稱()

let someVehicle = Vehicle()

在創建 Vehicle 新實例時,你可以訪問其訪問描述屬性來印出車輛當前速度的可讀描述:

Vehicle 定義了任意車輛的共同特徵,但其本身並沒有太多的用途。為了使其更有用,你需要對其進行優化來描述更具體的車輛類型。

# Subclassing

Subclassing 是在現有 class 上創建新的 class 的行為。subClass 繼承現有 class 的特徵,然後可以對其進行重新定義。你還可以向 subClass 添加新特徵:

要表示 subClass 具有 superClass,請在 subClass 之後編寫 superClass 名稱,使用冒號 (:) 分隔:

class SomeSubclass: SomeSuperclass {
// 在這邊定義 subClass
}

下面我們定義了名為 Bicycle 的 subClass,包含一個 Vehicle 的 superClass:

新的 Bicycle class 自動獲取 Vehicle 的所有特性,例如 currentSpeeddescription 屬性和 makeNoise 方法。

除了它繼承的特性以外,Bicycle 還定義了一個新的存儲屬性 hasBasket,其默認值為 false

默認情況下,你所創建的任何 Bicycle 實例都沒有籃子(hasBasket 默認為 false)。在創建實例之後,你可以將特定 Bicycle 實例的 hasBasket 屬性設為 true

你也能夠修改 Bicycle 實例繼承的 currentSpeed 屬性,並查詢實例繼承的 description 屬性。

SubClasses 本身也可以被繼承,下面創建了一個雙人座的腳踏車的 subClass 稱為 tandem

Tandem 繼承了 Bicycle 中所有的屬性及方法,而 Bicycle 繼承了 Vehicle 的所有屬性及方法。Tandem 還添加了一個名為 currentNumberOfPassengers 的存儲屬性,默認為 0

如果創建 Tandem 實例,則可以使用其任何新增的和繼承的屬性,並且查詢它從 Vehicle 繼承的唯讀 description 屬性:

# Overriding

SubClass 能夠提供自己的實例方法、類型方法、實例屬性、類型屬性或是下標的自定義實現,否則他將從 superClass 中繼承,這稱之為 overriding

要覆蓋特性否則他將會是繼承的特性,請使用 override 關鍵字作為覆寫定義的的前綴。如此以來可以闡述你打算提供覆寫並且沒有錯誤的提供匹配的定義。意外的覆寫可能導致意外行為,並且在編輯程式碼時,沒有 override 關鍵字的任何覆寫都會被判斷為錯誤。

override 關鍵字還提示 Swift 編譯器檢查你所覆寫的 superClass(或是父類其中ㄧ)是否具有與之相匹配的宣告來供你覆寫。這個檢查可以確保你覆寫的定義是正確的。

# 訪問 SuperClass 方法、屬性及下標

當你提供 subClass 方法、屬性或是下標的覆寫時,將現有 superClass 實現作為覆寫的一部份有時很有用。例如,你可以優化現有實現的行為,或將修改後的值存儲在現有繼承的變數中。

如果這是合適的,你可以使用 super 前綴來訪問方法、屬性或下標的 superClass 版本

  • 一個名為 someMethod() 的覆寫方法可以透過在覆寫方法實現中調用 super.someMethod() 來調用 superClass 的 someMethod()
  • 一個名為 someProperty 的覆寫屬性可以透過在覆寫 gettersetter 中以 super.someProperty 來訪問 superClasssomeProperty
  • 一個名為 someIndex 的覆寫下標可以使用 super[someIndex] 在覆寫的下標實現中訪問 superClass 中相同的下標。

# Overriding Methods

你可以覆寫繼承的實例或類型方法,來在 subClass 中提供定製或替代實現。

下面定義了一個名為 TrainVehicle 新的 subClass,它覆寫了 TrainVehicle 中繼承的 makeNoise() 方法:

如果你創建一個 Train 實例並且調用其 makeNoise() 方法,你可以看到該方法的 Train 的 subClass 版本被調用:

# Overriding Properties

你可以覆寫繼承的實例或類型屬性,來為該屬性提供自定義的 gettersetter,或是添加屬性觀察器來使覆寫的屬性能夠在底層屬性值更改時進行觀察。

● Overriding Property Getters 和 Setters

你可以提供自定義的 gettersetter(如果適用)來覆寫任何繼承的屬性,無論繼承的屬性是在來源中實現為存儲屬性還是計算屬性。SubClass 不知道繼承的屬性的存儲或計算性質,他只知道繼承的屬性具有特定的名稱和類型。你必須始終宣告要覆寫的屬性的名稱和類型,來使編譯器能夠檢查你的覆寫是否具有相同名稱和類型的 superClass 的屬性匹配。

透過在 subClass 屬性中覆寫提供 gettersetter,可以將繼承的唯讀屬性作為讀寫屬性提供。但是,你無法將繼承的讀寫屬性顯示為唯讀屬性。

如果將 setter 作為屬性覆寫的一部份提供,則還必須要為該覆寫提供 getter。如果你不想在覆寫 getter 中修改繼承屬性的值,則可以透過從 getter 中返回 super.someProperty 來簡單的傳遞繼承的值,其中的 someProperty 是你要覆寫的屬性名稱。

下面是犯了一個稱為 Carclass,它為 Vehicle 的 subClass。Car 引入了一個名為 gear 的新存儲屬性,默認值為 1Car 還會覆寫它從 Vehicle 繼承的 description 屬性,來提供包含當前檔位的自定義描述:

覆寫的 description 屬性首先調用 super.description,它返回 Vehicledescription 屬性。然後,Car 的描述版本在此描述的末尾添加了一些額外的訊息,來提供關於當前 gear 的訊息。

如果你創建 Car 的實例並且設置 gearcurrentSpeed 屬性,則可以看到其 description 屬性返回 Car 中定義的定製描述:

● Overriding 屬性觀察器

你可以使用屬性覆寫來添加屬性觀察器到繼承的屬性。這使你能夠在繼承的屬性值更改時收到通知,無論該屬性最初如何實現。

你不能將屬性觀察器添加到繼承的常數存儲屬性或繼承的唯讀計算屬性。無法設置這些屬性的值,因此不適合將 willSetdidSet 實現作為覆寫的一部份來提供。

另外請注意,你不能為同一個屬性提供覆寫的 setter 和覆寫屬性觀察器。如果想要觀察屬性值得更改,並且已經為該屬性提供了自定義 setter,則只需要觀察自定義 setter 中的任何值更改即可。

下面的範例定義了一個名為 AutomaticCarclass,為 Car 的 subClass。Automatic 表示帶有自動變速器的汽車,它會根據當前的速度自動選擇合適的檔位:

無論何時設置 AutomaticCar 實例的 currentSpeed 屬性,其屬性中的 didSet 觀察器都會將實例中的 gear 屬性設置為適合速度的檔位:

# 防止覆寫

你可以透過將方法、屬性或下標標記為 final防止其被覆寫。在方法、屬性及下標的之前編寫 final 修飾符來達成此操作( 像是: final varfinal funcfinal class funcfinal subscript)。

任何嘗試覆寫 subClass 中 final 方法、屬性或是下標都會表示編譯錯誤。你添加到擴展(extension)中的 class 的方法、屬性和下標也可以在擴展定義中標記為 final

你可以標記整個 class 為 final(final class)。,透過編寫 final 修飾符在 class 關鍵字之前任何對 final class 做 subclass 的嘗試都會表示編輯錯誤。

--

--

Jeremy Xue
Jeremy Xue ‘s Blog

Hi, I’m Jeremy. [好想工作室 — iOS Developer]