2.13 继承

原文链接: https://docs.swift.org/latest/documentation/the-swift-programming-language/inheritance/

2.13 继承

用子类添加或覆盖功能。

一个类可以继承另一个类的方法、属性及其他特性。当一个类继承另一个类时,继承的类称为子类,被继承的类称为它的父类。继承是区分 Swift 中类与其他类型的一项基本行为。

Swift 中的类可以调用和访问其父类的方法、属性和下标,也可以为这些方法、属性和下标提供自己的覆盖版本,以改进或修改它们的行为。Swift 会检查覆盖定义与父类定义是否匹配,从而帮助确保覆盖正确无误。

类还可以为继承来的属性添加属性观察器,以便在属性值变化时得到通知。任何属性都可以添加属性观察器,无论它最初定义为存储属性还是计算属性。

定义基类

任何不继承其他类的类都称为基类。

注意:Swift 的类不继承自某个统一的基类。 你定义类时若不指定父类, 它就自动成为供你继续构建的基类。

下面的例子定义了一个名为 Vehicle 的基类。这个基类定义了一个名为 currentSpeed 的存储属性,默认值为 0.0(属性类型被推断为 Double)。currentSpeed 属性的值被一个名为 description 的只读计算 String 属性用来生成对车辆的描述。

Vehicle 基类还定义了一个名为 makeNoise 的方法。对于基类 Vehicle 的实例它实际上什么也不做,但稍后会被 Vehicle 的子类定制:

1
2
3
4
5
6
7
8
9
class Vehicle {
    var currentSpeed = 0.0
    var description: String {
        return "traveling at \(currentSpeed) miles per hour"
    }
    func makeNoise() {
        // 什么也不做——任意一辆车并不一定会发出声音
    }
}

你用构造器语法创建 Vehicle 的新实例,也就是类型名后跟一对空圆括号:

1
let someVehicle = Vehicle()

创建了新的 Vehicle 实例之后,你可以访问它的 description 属性,打印出车辆当前速度的人类可读描述:

1
2
print("Vehicle: \(someVehicle.description)")
// Vehicle: traveling at 0.0 miles per hour

Vehicle 类定义了任意车辆的共同特性,但它本身用处不大。要让它更有用,就需要对它加以细化,描述更具体的车辆种类。

子类化

子类化是在已有类的基础上建立新类的行为。子类继承已有类的特性,你可以对这些特性加以细化,也可以为子类添加新特性。

要表示某个子类有父类,就把子类名写在父类名之前,用冒号分隔:

1
2
3
class SomeSubclass: SomeSuperclass {
    // 子类定义写在这里
}

下面的例子定义了一个名为 Bicycle 的子类,它的父类是 Vehicle:

1
2
3
class Bicycle: Vehicle {
    var hasBasket = false
}

新的 Bicycle 类自动获得 Vehicle 的所有特性,例如它的 currentSpeed、description 属性和 makeNoise() 方法。

除了继承来的特性,Bicycle 类还定义了一个新的存储属性 hasBasket,默认值为 false(推断出该属性的类型为 Bool)。

默认情况下,你创建的每个新 Bicycle 实例都没有车筐。你可以在某个 Bicycle 实例创建之后,把它的 hasBasket 属性设为 true:

1
2
let bicycle = Bicycle()
bicycle.hasBasket = true

你也可以修改 Bicycle 实例继承来的 currentSpeed 属性,并查询该实例继承来的 description 属性:

1
2
3
bicycle.currentSpeed = 15.0
print("Bicycle: \(bicycle.description)")
// Bicycle: traveling at 15.0 miles per hour

子类本身还可以再被继承。下一个例子为 Bicycle 创建了一个子类,表示被称为"双人车"的双座自行车:

1
2
3
class Tandem: Bicycle {
    var currentNumberOfPassengers = 0
}

Tandem 继承了 Bicycle 的所有属性和方法,而 Bicycle 又继承了 Vehicle 的所有属性和方法。Tandem 子类还添加了一个名为 currentNumberOfPassengers 的新存储属性,默认值为 0。

如果你创建一个 Tandem 实例,就可以使用它新增的和继承来的任何属性,并查询它从 Vehicle 继承来的只读 description 属性:

1
2
3
4
5
6
let tandem = Tandem()
tandem.hasBasket = true
tandem.currentNumberOfPassengers = 2
tandem.currentSpeed = 22.0
print("Tandem: \(tandem.description)")
// Tandem: traveling at 22.0 miles per hour

覆盖

子类可以为它原本会从父类继承的实例方法、类型方法、实例属性、类型属性或下标提供自己的自定义实现,这称为覆盖。

要覆盖原本会继承来的特性,就在覆盖定义前加上 override 关键字。这样做既表明你确实想提供一个覆盖,也表明你不是误写了一个同名的定义。意外覆盖可能导致意外行为,任何缺少 override 关键字的覆盖都会在编译代码时被诊断为错误。

override 关键字还会促使 Swift 编译器检查你正在覆盖的类的父类(或它的某个祖先)是否有与你提供的覆盖相匹配的声明,从而确保你的覆盖定义是正确的。

访问父类的方法、属性和下标

当你在子类中覆盖某个方法、属性或下标时,有时把父类已有的实现作为覆盖的一部分使用会很有用。例如,你可以改进该已有实现的行为,或者把修改后的值存进继承来的变量。

在这种情况下,你用 super 前缀访问父类版本的方法、属性或下标:

  • 名为 someMethod() 的被覆盖方法,可以在覆盖实现中调用 super.someMethod() 来调用父类版本的 someMethod()。
  • 名为 someProperty 的被覆盖属性,可以在覆盖的取值器或设值器实现中用 super.someProperty 访问父类版本的 someProperty。
  • 针对 someIndex 的被覆盖下标,可以在覆盖的下标实现中用 super[someIndex] 访问父类中同一个下标的版本。

覆盖方法

你可以覆盖继承来的实例方法或类型方法,在子类中为该方法提供量身定制或替代性的实现。

下面的例子为 Vehicle 定义了一个名为 Train 的新子类,它覆盖了 Train 从 Vehicle 继承来的 makeNoise() 方法:

1
2
3
4
5
class Train: Vehicle {
    override func makeNoise() {
        print("Choo Choo")
    }
}

如果你创建一个新的 Train 实例并调用它的 makeNoise() 方法,就会看到调用的是 Train 子类版本的方法:

1
2
3
let train = Train()
train.makeNoise()
// 输出 "Choo Choo"。

覆盖属性

你可以覆盖继承来的实例属性或类型属性,为该属性提供自定义的取值器和设值器,或者添加属性观察器,使被覆盖的属性能够观察底层属性值的变化。

覆盖属性的取值器与设值器

你可以提供自定义的取值器(以及适当的设值器)来覆盖任何继承来的属性,无论该属性在源代码中实现为存储属性还是计算属性。子类并不知道继承来的属性是存储属性还是计算属性——它只知道这个继承来的属性有某个名字和类型。你必须始终同时写出被覆盖属性的名字和类型,以便编译器检查你的覆盖是否与同名同类型的父类属性匹配。

你可以在子类的属性覆盖中同时提供取值器和设值器,把继承来的只读属性呈现为读写属性;但你不能把继承来的读写属性呈现为只读属性。

注意:如果你在属性覆盖中提供了设值器, 就必须同时为该覆盖提供取值器。 如果你不想在覆盖的取值器中修改继承属性的值, 可以在取值器中返回 super.someProperty 来直接沿用继承来的值, 其中 someProperty 是你要覆盖的属性名。

下面的例子定义了一个新类 Car,它是 Vehicle 的子类。Car 类引入了一个名为 gear 的新存储属性,默认整数值为 1。Car 类还覆盖了它从 Vehicle 继承来的 description 属性,提供包含当前挡位的自定义描述:

1
2
3
4
5
6
class Car: Vehicle {
    var gear = 1
    override var description: String {
        return super.description + " in gear \(gear)"
    }
}

对 description 属性的覆盖首先调用 super.description,返回 Vehicle 类的 description 属性;随后 Car 类版本的 description 在这段描述末尾追加一些文字,提供当前挡位的信息。

如果你创建一个 Car 类实例并设置它的 gear 和 currentSpeed 属性,就能看到它的 description 属性返回的是 Car 类中定义的定制描述:

1
2
3
4
5
let car = Car()
car.currentSpeed = 25.0
car.gear = 3
print("Car: \(car.description)")
// Car: traveling at 25.0 miles per hour in gear 3

覆盖属性观察器

你可以用属性覆盖为继承来的属性添加属性观察器,从而在该继承属性的值变化时得到通知,无论这个属性最初是如何实现的。关于属性观察器的更多内容,参见属性观察器。

注意:你不能为继承来的常量存储属性 或继承来的只读计算属性添加属性观察器。 这些属性的值无法设置, 因此作为覆盖的一部分提供 willSet 或 didSet 实现并不合适。

另请注意,你不能为同一个属性同时提供 覆盖设值器和覆盖属性观察器。 如果你想观察属性值的变化, 而已经为该属性提供了自定义设值器, 那么直接在自定义设值器中观察值的变化即可。

下面的例子定义了一个新类 AutomaticCar,它是 Car 的子类。AutomaticCar 类表示一辆带自动变速箱的汽车,它会根据当前车速自动选择合适的挡位:

1
2
3
4
5
6
7
class AutomaticCar: Car {
    override var currentSpeed: Double {
        didSet {
            gear = Int(currentSpeed / 10.0) + 1
        }
    }
}

每当你设置 AutomaticCar 实例的 currentSpeed 属性时,该属性的 didSet 观察器就把实例的 gear 属性设为适合新车速的挡位。具体来说,观察器选择的挡位是新 currentSpeed 值除以 10 之后向下取整再加 1。速度 35.0 对应的挡位是 4:

1
2
3
4
let automatic = AutomaticCar()
automatic.currentSpeed = 35.0
print("AutomaticCar: \(automatic.description)")
// AutomaticCar: traveling at 35.0 miles per hour in gear 4

阻止覆盖

把方法、属性或下标标记为 final,就可以防止它被覆盖。为此,把 final 修饰符写在方法、属性或下标的引入关键字之前(例如 final var、final func、final class func 和 final subscript)。

任何在子类中覆盖 final 方法、属性或下标的尝试都会被报告为编译期错误。你在扩展中为类添加的方法、属性或下标,也可以在扩展的定义中标记为 final。更多内容参见扩展。

在类的定义中把 final 修饰符写在 class 关键字之前(即 final class),就可以把整个类标记为 final。任何继承 final 类的尝试都会被报告为编译期错误。