Swift中文教程(十三) 繼承

ZFJ_張福傑發表於2016-02-23

一個類可以從另外一個類中繼承方法,屬性或者其它的一些特性。當一個類繼承於另外一個類時,這個繼承的類叫子類,被繼承的類叫父類。繼承是Swift中類區別於其它型別的一個基本特徵。

Swift中的類可以呼叫父類的方法,使用父類的屬性和下標,還可以根據需要使用重寫方法或者屬性來重新定義和修改他們的一些特性。Swift可以幫助你檢查重寫的方法和父類的方法定義是相符的。

類還可以為它繼承的屬性新增觀察者,這樣可以能夠讓它在一個屬性變化的時候得到通知。屬性觀察者可以被新增給任何屬性,不管它之前是儲存屬性還是計算屬性。

 

1、定義一個基類

任何一個不繼承於其它類的類被稱作基類

注意:Swift的類不是從一個全域性基類繼承而來。在你編寫程式碼的時,只要是在類的定義中沒有繼承自父類的類都是基類。

下面的例子定義了一個叫Vehicle的基類。基類包含兩個所有交通工具通用的屬性numberOfWheels和maxPassengers。這兩個屬性被一個叫description的方法使用,通過返回一個String描述來作為這個交通工具的特徵:

1
2
3
4
5
6
7
8
9
10
11
class Vehicle {
    var numberOfWheels: Int
    var maxPassengers: Int
    func description() -> String {
        return "\(numberOfWheels) wheels; up to \(maxPassengers) passengers"
    }
    init() {
        numberOfWheels = 0
        maxPassengers = 1
    }
}

這個交通工具類Vehicle還定義了一個建構函式來設定它的屬性。建構函式更多的解釋在Initialization一章,但是為了說明子類如何修改繼承的屬性,這裡需要簡要解釋一下什麼叫建構函式。

通過建構函式可以建立一個型別的例項。儘管建構函式不是方法,但是它們在編碼的時候使用了非常相似的語法。建構函式通過確保所有例項的屬性都是有效的來建立一個新的例項。

建構函式最簡單的形式是使用init關鍵詞的一個類似方法的函式,並且沒有任何引數:

1
2
3
init() {
    // perform some initialization here
}

使用建構函式語法TypeName和空的兩個小括號來完成一個Vehicle例項的建立:

1
let someVehicle = Vehicle()

Vehicle的建構函式為屬性設定了一些初始值(numberOfWheels = 0 然後 maxPassengers = 1)。

Vehicle類定義的是一個通用的交通工具特性,它本身沒有太多意義,所以就需要衝定義它的一些屬性或者方法來讓它具有實際的意義。

 

2、產生子類

產生子類就是根據一個已有的類產生新類的過程。子類繼承了父類的一些可以修改的特性。還可以為子類新增一些新的特性。

為了表明一個類是繼承自一個父類,需要將父類的名稱寫在子類的後面,並且用冒號分隔:

1
2
3
class SomeClass: SomeSuperclass {
    // class definition goes here
}

下面的例子定義了一種特定叫Bicycle的交通工具。這個新類是基於已有的類Vehicle產生的。書寫方式是在類名Bicycle後加冒號加父類Vehicle名。

可以理解為:

定義一個新的類叫Bicycle,它繼承了Vehicle的特性:

1
2
3
4
5
6
class Bicycle: Vehicle {
    init() {
        super.init()
        numberOfWheels = 2
    }
}

Bicycle是Vehicle的子類,Vehicle是Bicycle的父類。Bicycle類繼承了Vehicle所有的特徵,比如maxPassengers和numberOfWheels屬性。你還可以為Bicycle類新增心的屬性。

Bicycle類也定義了建構函式,在這個建構函式中呼叫了父類的建構函式super.init(),這樣可以確保在Bicycle修改他們之前,父類已經初始化了。

注意:跟Objective-C不同的是,Swift中的建構函式沒有預設繼承。更多資訊可以參考Initializer Inheritance and Overriding這一章節。

maxPassengers屬性在繼承自父類的時候已經被初始化了,對於Bicycle來說是正確的,因此不需要再做更改。然後numberOfWheels是不對的,所以被替換成了2.

不僅屬性是繼承於Vehicle的,Bicycle還繼承了父類的方法。如果你建立一個例項,然後呼叫了已經繼承的description方法,可以得到該交通工具的描述並且看到它的屬性已經被修改:

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

子類本身也可以作為父類被再次繼承:

1
2
3
4
5
6
class Tandem: Bicycle {
    init() {
        super.init()
        maxPassengers = 2
    }
}

上面的例子建立了Bicycle的子類,叫做tandem,也就可以兩個人一起騎的自行車。所以Tandem沒有修改numberOfWheels屬性,只是更新了maxPassengers屬性。

注意:子類只能夠在構造的時候修改變數的屬性,不能修改常量的屬性。

建立一個Tandem的例項,然後呼叫description方法檢查屬性是否被正確修改:

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

注意到description方法也被Tandem繼承了。

 

3、重寫方法

子類可以提供由父類繼承來的例項方法,類方法,例項屬性或者下標的個性化實現。這個特性被稱為重寫。

重寫一個由繼承而來的方法需要在方法定義前標註override關鍵詞。通過這樣的操作可以確保你所要修改的這個方法確實是繼承而來的,而不會出現重寫錯誤。錯誤的重寫會造成一些不可預知的錯誤,所以如果如果不標記override關鍵詞的話,就會被在程式碼編譯時報錯。

override關鍵詞還能夠讓Swift編譯器檢查該類的父類是否有相符的方法,以確保你的重寫是可用的,正確的。

訪問父類方法,屬性和下標

當在重寫子類繼承自父類的方法,屬性或者下標的時候,需要用到一部分父類已有的實現。比如你可以重定義已知的一個實現或者在繼承的變數中儲存一個修改的值。

適當的時候,可以通過使用super字首來訪問父類的方法,屬性或者下標:

叫someMethod的重寫方法可以在實現的時候通過super.someMethod()呼叫父類的someMethod方法。

叫someProperty的重寫屬性可以在重寫實現getter或者setter的時候通過super.someProperty呼叫父類的someProperty。

叫someIndex的重寫下標可以在實現下標的時候通過super[someIndex]來訪問父類的下標。

複寫方法

你可以在你的子類中實現定製的繼承於父類的例項方法或者類方法。

下面的例子演示的就是一個叫Car的Vehicle子類,重寫了繼承自Vehicle的description方法。

1
2
3
4
5
6
7
8
9
10
11
12
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中定義了一個新的Double型別的儲存屬性speed。這個屬性預設值是0.0,意思是每小時0英里。Car還有一個自定義的建構函式,設定了最大乘客數為5,輪子數量是4.

Car重寫了繼承的description方法,並在方法名description前標註了override關鍵詞。

在description中並沒有給出了一個全新的描述實現,還是通過super.description使用了Vehicle提供的部分描述語句,然後加上了自己定義的一些屬性,如當前速度。

如果你建立一個Car的例項,然後呼叫description方法,會發現描述語句變成了這樣:

1
2
3
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中修改繼承的屬性的值,可以在getter中使用super.someProperty即可,在下面SpeedLimitedCar例子中也是這樣。

下面的例子定義了一個新類SpeedLimitedCar,是Car的一個子類。這個類表示一個顯示在40碼一下的車輛。通過重寫繼承的speed屬性來實現:

1
2
3
4
5
6
7
8
9
10
class SpeedLimitedCar: Car {
    override var speed: Double  {
    get {
        return super.speed
    }
    set {
        super.speed = min(newValue, 40.0)
    }
    }
}

每當你要設定speed屬性的時候,setter都會檢查新值是否比40大,二者中較小的值會被設定給SpeedLimitedCar。

如果你嘗試為speed設定超過40的值,description的輸出依然還是40:

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

重寫屬性觀察者

你可以使用屬性重寫為繼承的屬性新增觀察者。這種做法可以讓你無論這個屬性之前是如何實現的,在繼承的這個屬性變化的時候都能得到提醒。更多相關的資訊可以參考Property Observers這章。

注意:不能為繼承的常量儲存屬性或者是隻讀計算屬性新增觀察者。這些屬性值是不能被修改的,因此不適合在重寫實現時新增willSet或者didSet方法。

注意:不能同時定義重寫setter和重寫屬性觀察者,如果想要觀察屬性值的變化,並且又為該屬性給出了定製的setter,那隻需要在setter中直接獲得屬性值的變化就行了。

下面的程式碼演示的是一個新類AutomaticCar,也是Car的一個子類。這個類表明一個擁有自動變速箱的汽車,可以根據現在的速度自動選擇檔位,並在description中輸出當前檔位:

1
2
3
4
5
6
7
8
9
10
11
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)"
    }
}

這樣就可以實現,每次你設定speed的值的時候,didSet方法都會被呼叫,來看檔位是否需要變化。gear是由speed除以10加1計算得來,所以當速度為35的時候,gear檔位為4:

1
2
3
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

 

4、禁止重寫

你可以通過標記final關鍵詞來禁止重寫一個類的方法,屬性或者下標。在定義的關鍵詞前面標註@final屬性即可。

在子類中任何嘗試重寫父類的final方法,屬性或者下標的行為都會在編譯時報錯。同樣在擴充套件中為類新增的方法,屬性或者下標也可以被標記為final。

還可以在類關鍵詞class前使用@final標記一整個類為final(@final class)。任何子類嘗試繼承這個父類時都會在編譯時報錯。

 

感謝翻譯小組成員:李起攀(微博)、若晨(微博)、YAO、粽子、山有木兮木有枝、渺-Bessie、墨離、矮人王、CXH、Tiger大顧(微博)
個人轉載請註明出處和原始連結http://letsswift.com/2014/06/inheritance

相關文章