翻譯:Hawstein 校對:menlongsheng

繼承(Inheritance)


本頁包含內容:

一個類別可以繼承(inherit)另一個類別的方法(methods),屬性(property)和其它特性。當一個類別繼承其它類別時,繼承類別叫子類別(subclass),被繼承類別叫超類別(或父類別,superclass)。在 Swift 中,繼承是區分「類別」與其它型別的一個基本特征。

在 Swift 中,類別可以呼叫和存取超類別的方法,屬性和下標腳本(subscripts),並且可以重寫(override)這些方法,屬性和下標腳本來優化或修改它們的行為。Swift 會檢查你的重寫定義在超類別中是否有匹配的定義,以此確保你的重寫行為是正確的。

可以為類別中繼承來的屬性添加屬性觀察器(property observer),這樣一來,當屬性值改變時,類別就會被通知到。可以為任何屬性添加屬性觀察器,無論它原本被定義為儲存型屬性(stored property)還是計算型屬性(computed property)。

定義一個基類別(Base class)

不繼承於其它類別的類別,稱之為基類別(base calss)

注意:
Swift 中的類別並不是從一個通用的基類別繼承而來。如果你不為你定義的類別指定一個超類別的話,這個類別就自動成為基類別。

下面的範例定義了一個叫Vehicle的基類別。這個基類別宣告了兩個對所有車輛都通用的屬性(numberOfWheelsmaxPassengers)。這些屬性在description方法中使用,這個方法回傳一個String型別的,對車輛特征的描述:

class Vehicle {
    var numberOfWheels: Int
    var maxPassengers: Int
    func description() -> String {
        return "\(numberOfWheels) wheels; up to \(maxPassengers) passengers"
    }
    init() {
        numberOfWheels = 0
        maxPassengers = 1
    }
}

Vehicle類別定義了建構器(initializer)來設置屬性的值。建構器會在建構過程一節中詳細介紹,這裡我們做一下簡單介紹,以便於講解子類別中繼承來的屬性如何被修改。

建構器用於創建某個型別的一個新實例。儘管建構器並不是方法,但在語法上,兩者很相似。建構器的工作是準備新實例以供使用,並確保實例中的所有屬性都擁有有效的初始化值。

建構器的最簡單形式就像一個沒有參數的實例方法,使用init關鍵字:

init() {
    // 執行建構過程
}

如果要創建一個Vehicle類別的新實例,使用建構器語法呼叫上面的初始化器,即類別名後面跟一個空的小括號:

let someVehicle = Vehicle()

這個Vehicle類別的建構器為任意的一輛車設置一些初始化屬性值(numberOfWheels = 0maxPassengers = 1)。

Vehicle類別定義了車輛的共同特性,但這個類別本身並沒太大用處。為了使它更為實用,你需要進一步細化它來描述更具體的車輛。

子類別生成(Subclassing)

子類別生成(Subclassing)指的是在一個已有類別的基礎上創建一個新的類別。子類別繼承超類別的特性,並且可以優化或改變它。你還可以為子類別添加新的特性。

為了指明某個類別的超類別,將超類別名寫在子類別名的後面,用冒號分隔:

class SomeClass: SomeSuperclass {
    // 類別的定義
}

下一個範例,定義一個更具體的車輛類別叫Bicycle。這個新類別是在 Vehicle類別的基礎上創建起來。因此你需要將Vehicle類別放在 Bicycle類別後面,用冒號分隔。

我們可以將這讀作:

「定義一個新的類別叫Bicycle,它繼承了Vehicle的特性」;

class Bicycle: Vehicle {
    init() {
        super.init()
        numberOfWheels = 2
    }
}

preview BicycleVehicle的子類別,VehicleBicycle的超類別。新的Bicycle類別自動獲得Vehicle類別的特性,比如 maxPassengersnumberOfWheels屬性。你可以在子類別中定制這些特性,或添加新的特性來更好地描述Bicycle類別。

Bicycle類別定義了一個建構器來設置它定制的特性(自行車只有2個輪子)。Bicycle的建構器呼叫了它父類別Vehicle的建構器 super.init(),以此確保在Bicycle類別試圖修改那些繼承來的屬性前Vehicle類別已經初始化過它們了。

注意:
不像 Objective-C,在 Swift 中,初始化器預設是不繼承的,見初始化器的繼承與重寫

Vehicle類別中maxPassengers的預設值對自行車來說已經是正確的,因此在Bicycle的建構器中並沒有改變它。而numberOfWheels原來的值對自行車來說是不正確的,因此在初始化器中將它更改為 2。

Bicycle不僅可以繼承Vehicle的屬性,還可以繼承它的方法。如果你創建了一個Bicycle類別的實例,你就可以呼叫它繼承來的description方法,並且可以看到,它輸出的屬性值已經發生了變化:

let bicycle = Bicycle()
println("Bicycle: \(bicycle.description())")
// Bicycle: 2 wheels; up to 1 passengers

子類別還可以繼續被其它類別繼承:

class Tandem: Bicycle {
    init() {
        super.init()
        maxPassengers = 2
    }
}

上面的範例創建了Bicycle的一個子類別:雙人自行車(tandem)。TandemBicycle繼承了兩個屬性,而這兩個屬性是BicycleVehicle繼承而來的。Tandem並不修改輪子的數量,因為它仍是一輛自行車,有 2 個輪子。但它需要修改maxPassengers的值,因為雙人自行車可以坐兩個人。

注意:
子類別只允許修改從超類別繼承來的變數屬性,而不能修改繼承來的常數屬性。

創建一個Tandem類別的實例,列印它的描述,即可看到它的屬性已被更新:

let tandem = Tandem()
println("Tandem: \(tandem.description())")
// Tandem: 2 wheels; up to 2 passengers

注意,Tandem類別也繼承了description方法。一個類別的實例方法會被這個類別的所有子類別繼承。

重寫(Overriding)

子類別可以為繼承來的實例方法(instance method),類別方法(class method),實例屬性(instance property),或下標腳本(subscript)提供自己定制的實作(implementation)。我們把這種行為叫重寫(overriding)

如果要重寫某個特性,你需要在重寫定義的前面加上override關鍵字。這麼做,你就表明了你是想提供一個重寫版本,而非錯誤地提供了一個相同的定義。意外的重寫行為可能會導致不可預知的錯誤,任何缺少override關鍵字的重寫都會在編譯時被診斷為錯誤。

override關鍵字會提醒 Swift 編譯器去檢查該類別的超類別(或其中一個父類別)是否有匹配重寫版本的宣告。這個檢查可以確保你的重寫定義是正確的。

存取超類別的方法,屬性及下標腳本

當你在子類別中重寫超類別的方法,屬性或下標腳本時,有時在你的重寫版本中使用已經存在的超類別實作會大有裨益。比如,你可以優化已有實作的行為,或在一個繼承來的變數中儲存一個修改過的值。

在合適的地方,你可以通過使用super前綴來存取超類別版本的方法,屬性或下標腳本:

  • 在方法someMethod的重寫實作中,可以通過super.someMethod()來呼叫超類別版本的someMethod方法。
  • 在屬性someProperty的 getter 或 setter 的重寫實作中,可以通過super.someProperty來存取超類別版本的someProperty屬性。
  • 在下標腳本的重寫實作中,可以通過super[someIndex]來存取超類別版本中的相同下標腳本。

重寫方法

在子類別中,你可以重寫繼承來的實例方法或類別方法,提供一個定制或替代的方法實作。

下面的範例定義了Vehicle的一個新的子類別,叫Car,它重寫了從Vehicle類別繼承來的description方法:

class Car: Vehicle {
    var speed: Double = 0.0
    init() {
        super.init()
        maxPassengers = 5
        numberOfWheels = 4
    }
    override func description() -> String {
        return super.description() + "; "
            + "traveling at \(speed) mph"
    }
}

Car宣告了一個新的儲存型屬性speed,它是Double型別的,預設值是0.0,表示「時速是0英裡」。Car有自己的初始化器,它將乘客的最大數量設為5,輪子數量設為4。

Car重寫了繼承來的description方法,它的宣告與Vehicle中的description方法一致,宣告前面加上了override關鍵字。

Car中的description方法並非完全自定義,而是通過super.description使用了超類別Vehicle中的description方法,然後再追加一些額外的資訊,比如汽車的當前速度。

如果你創建一個Car的新實例,並列印description方法的輸出,你就會發現描述資訊已經發生了改變:

let car = Car()
println("Car: \(car.description())")
// Car: 4 wheels; up to 5 passengers; traveling at 0.0 mph

重寫屬性

你可以重寫繼承來的實例屬性或類別屬性,提供自己定制的getter和setter,或添加屬性觀察器使重寫的屬性觀察屬性值什麼時候發生改變。

重寫屬性的Getters和Setters

你可以提供定制的 getter(或 setter)來重寫任意繼承來的屬性,無論繼承來的屬性是儲存型的還是計算型的屬性。子類別並不知道繼承來的屬性是儲存型的還是計算型的,它只知道繼承來的屬性會有一個名字和型別。你在重寫一個屬性時,必需將它的名字和型別都寫出來。這樣才能使編譯器去檢查你重寫的屬性是與超類別中同名同型別的屬性相匹配的。

你可以將一個繼承來的唯讀屬性重寫為一個讀寫屬性,只需要你在重寫版本的屬性裡提供 getter 和 setter 即可。但是,你不可以將一個繼承來的讀寫屬性重寫為一個唯讀屬性。

注意:
如果你在重寫屬性中提供了 setter,那麼你也一定要提供 getter。如果你不想在重寫版本中的 getter 裡修改繼承來的屬性值,你可以直接回傳super.someProperty來回傳繼承來的值。正如下面的SpeedLimitedCar的範例所示。

以下的範例定義了一個新類別,叫SpeedLimitedCar,它是Car的子類別。類別SpeedLimitedCar表示安裝了限速裝置的車,它的最高速度只能達到40mph。你可以通過重寫繼承來的speed屬性來實作這個速度限制:

class SpeedLimitedCar: Car {
    override var speed: Double  {
    get {
        return super.speed
    }
    set {
        super.speed = min(newValue, 40.0)
    }
    }
}

當你設置一個SpeedLimitedCar實例的speed屬性時,屬性setter的實作會去檢查新值與限制值40mph的大小,它會將超類別的speed設置為newValue40.0中較小的那個。這兩個值哪個較小由min函式決定,它是Swift標準函式庫中的一個全域函式。min函式接收兩個或更多的數,回傳其中最小的那個。

如果你嘗試將SpeedLimitedCar實例的speed屬性設置為一個大於40mph的數,然後列印description函式的輸出,你會發現速度被限制在40mph:

let limitedCar = SpeedLimitedCar()
limitedCar.speed = 60.0
println("SpeedLimitedCar: \(limitedCar.description())")
// SpeedLimitedCar: 4 wheels; up to 5 passengers; traveling at 40.0 mph

重寫屬性觀察器(Property Observer)

你可以在屬性重寫中為一個繼承來的屬性添加屬性觀察器。這樣一來,當繼承來的屬性值發生改變時,你就會被通知到,無論那個屬性原本是如何實作的。關於屬性觀察器的更多內容,請看屬性觀察器

注意:
你不可以為繼承來的常數儲存型屬性或繼承來的唯讀計算型屬性添加屬性觀察器。這些屬性的值是不可以被設置的,所以,為它們提供willSetdidSet實作是不恰當。此外還要注意,你不可以同時提供重寫的 setter 和重寫的屬性觀察器。如果你想觀察屬性值的變化,並且你已經為那個屬性提供了定制的 setter,那麼你在 setter 中就可以觀察到任何值變化了。

下面的範例定義了一個新類別叫AutomaticCar,它是Car的子類別。AutomaticCar表示自動擋汽車,它可以根據當前的速度自動選擇合適的擋位。AutomaticCar也提供了定制的description方法,可以輸出當前擋位。

class AutomaticCar: Car {
    var gear = 1
    override var speed: Double {
    didSet {
        gear = Int(speed / 10.0) + 1
    }
    }
    override func description() -> String {
        return super.description() + " in gear \(gear)"
    }
}

當你設置AutomaticCarspeed屬性,屬性的didSet觀察器就會自動地設置gear屬性,為新的速度選擇一個合適的擋位。具體來說就是,屬性觀察器將新的速度值除以10,然後向下取得最接近的整數值,最後加1來得到檔位gear的值。例如,速度為10.0時,擋位為1;速度為35.0時,擋位為4:

let automatic = AutomaticCar()
automatic.speed = 35.0
println("AutomaticCar: \(automatic.description())")
// AutomaticCar: 4 wheels; up to 5 passengers; traveling at 35.0 mph in gear 4

防止重寫

你可以通過把方法,屬性或下標腳本標記為final來防止它們被重寫,只需要在宣告關鍵字前加上@final特性即可。(例如:@final var, @final func, @final class func, 以及 @final subscript

如果你重寫了final方法,屬性或下標腳本,在編譯時會報錯。在擴展中,你添加到類別裡的方法,屬性或下標腳本也可以在擴展的定義裡標記為 final。

你可以通過在關鍵字class前添加@final特性(@final class)來將整個類別標記為 final 的,這樣的類別是不可被繼承的,否則會報編譯錯誤。