第13章:继承
类可以从另一个类继承方法,属性和其他特性。当一个类继承自另一个类时,继承类称为子类,它继承的类称为其超类(也可叫作父类)。继承是一种基本行为,它将类与Swift中的其他类型区分开来。
注意
在Swift中只有类可以继承,结构体和枚举不可以
Swift中的类可以调用和访问属于其超类的方法,属性和下标,并可以提供这些方法,属性和下标的自己的重写版本,以优化或修改它们的行为。Swift通过检查覆盖定义是否具有匹配的超类定义来帮助确保覆盖是正确的。
类还可以将属性观察器添加到继承的属性,以便在属性值更改时得到通知。属性观察者可以添加到任何属性,无论它最初是被定义为存储属性还是计算属性。
13.1 基类
在很多编程语言里,上述的超类、父类也会被叫作基类。但是在Swift里,基类是指一些不从任何类派生的类。
在Swift中类没有从唯一的一个根类派生,这与Objective-C不同(所有类都从NSObject派生)
// 定义了一个基类
class Vehicle {
var currentSpeed = 0.0
var description: String {
return "traveling at \(currentSpeed) miles per hour"
}
func makeNoise() {
// do nothing - an arbitrary vehicle doesn't necessarily make a noise
}
}
// 创建实例
let someVehicle = Vehicle()
// 访问实例的属性,该属性是一个只读的计算属性
print("Vehicle: \(someVehicle.description)")
// Vehicle: traveling at 0.0 miles per hour
13.2 子类
子类化是在现有类上创建新类的行为。子类继承现有类的特征,然后可以对其进行细化。您还可以向子类添加新特征。
// 要指示子类具有超类,请在超类名称之前写入子类名称,用冒号分隔:
class SomeSubclass: SomeSuperclass {
// subclass definition goes here
}
// 定义子类的实例
class Bicycle: Vehicle {
var hasBasket = false
}
新的Bicycle类自动获得所有的特性Vehicle,例如它的currentSpeed和description特性及其makeNoise()方法。除了它继承的特性之外,Bicycle该类还定义了一个新的存储属性,hasBasket其默认值为false(推断Bool属性的类型)。默认情况下,Bicycle您创建的任何新实例都不会有篮子。您可以在创建该实例后将hasBasket属性设置true为特定Bicycle实例:
let bicycle = Bicycle()
bicycle.hasBasket = true
// 还可以修改继承的currentSpeed属性,该属性属于超类:
bicycle.currentSpeed = 15.0
print("Bicycle: \(bicycle.description)")
// Bicycle: traveling at 15.0 miles per hour
子类本身可以是子类。下一个示例Bicycle为双座自行车创建了一个子类,称为“tandem”:
class Tandem: Bicycle {
var currentNumberOfPassengers = 0
}
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
13.2 覆盖
子类可以提供自己的实例方法,类型方法,实例属性,类型属性或下标的自定义实现,否则它将从超类继承。这被称为重写。
要覆盖否则将继承的特性,请使用override关键字为覆盖定义添加前缀。这样做可以澄清您打算提供覆盖并且没有错误地提供匹配的定义。意外覆盖可能会导致意外行为,并且override在编译代码时,没有关键字的任何覆盖都会被诊断为错误。
该override关键字还会提示Swift编译器检查您的重写类的超类(或其父类之一)是否具有与您为重写提供的声明匹配的声明。此检查可确保您的重写定义正确无误。
可以使用super关键字来调用超类的属性、方法等
- 通过super.someMethod()在重写方法实现中调用来调用超类版本。
- 通过super.someProperty在覆盖的getter或setter实现中访问超类的属性。
- 可以使用super[someIndex]从重写的下标实现中访问同一下标的超类版本。
13.2.1 覆盖方法
可以覆盖继承的实例或类型方法,以在子类中提供方法的定制或替代实现。以下示例定义了一个新的Vehicle被调用子类Train,它覆盖了从以下makeNoise()方法Train继承的方法Vehicle:
class Train: Vehicle {
override func makeNoise() {
print("Choo Choo")
}
}
// 如果你创建一个新实例Train并调用其makeNoise()方法,则可以看到该方法的Train子类版本被调用:
let train = Train()
train.makeNoise()
// Prints "Choo Choo"
13.2.2 覆盖属性
可以覆盖继承的实例或类型属性,以便为该属性提供自己的自定义getter和setter,或添加属性观察器以使覆盖属性能够在基础属性值更改时进行观察。
13.2.2.1 覆盖属性getter和setter
可以提供自定义getter(和setter,如果适用)以覆盖任何继承的属性,无论继承的属性是在超类实现为存储属性还是计算属性。子类不知道继承属性的存储或计算性质 - 它只知道继承的属性具有特定的名称和类型。您必须始终声明要覆盖的属性的名称和类型,以使编译器能够检查您的覆盖是否与具有相同名称和类型的超类属性匹配。
通过在子类属性覆盖中提供getter和setter,可以将继承的只读属性作为读写属性提供。但是,你不能将继承的读写属性显示为只读属性。
注意
如果您将setter作为属性覆盖的一部分提供,则还必须为该覆盖提供getter。如果您不想在重写getter中修改继承属性的值,则可以通过super.someProperty从getter 返回来简单地传递继承的值,其中someProperty是您要覆盖的属性的名称。
下面的示例定义了一个名为的新类Car,它是Vehicle的子类。该Car定义引入了新的存储属性gear,用默认整数值1。Car也覆盖了description它从继承属性Vehicle,以提供包括当前齿轮定制描述:
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类:
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
13.2.2.2 覆盖属性观察
可以使用属性重写将属性观察器添加到继承的属性。这使你可以在继承属性的值更改时收到通知,无论该属性最初如何实现。
注意
你不能将属性观察器添加到继承的常量存储属性或继承的只读计算属性。因为无法设置这些属性的值,因此不能提供willSet
或didSet
实现作为覆盖的一部分。
另请注意,你不能同时为同一属性提供重写setter和覆盖属性观察器。如果要观察属性值的更改,并且已经为该属性提供了自定义setter,则只需观察自定义setter中的任何值更改即可。
下面的示例定义了一个名为的新类AutomaticCar
,它是一个子类Car
。所述AutomaticCar
类表示用自动变速器,自动选择适当的齿轮使用基于当前速度一辆车:
1. class AutomaticCar: Car {
2. override var currentSpeed: Double {
3. didSet {
4. gear = Int(currentSpeed / 10.0) + 1
5. }
6. }
7. }
属性观察者选择一个新currentSpeed
值除以的齿轮,10
向下舍入到最接近的整数,加号1
。35.0
生产齿轮的速度4
:
1. let automatic = AutomaticCar()
2. automatic.currentSpeed = 35.0
3. print("AutomaticCar: \(automatic.description)")
4. // AutomaticCar: traveling at 35.0 miles per hour in gear 4
13.2.2 防止覆盖
可以通过将方法,属性或下标标记为final来阻止它被覆盖(final var、final func、final class func、final subscript)。
任何覆盖子类中的最终方法,属性或下标的尝试都会报告为编译时错误。添加到扩展中的类的方法,属性或下标也可以在扩展的定义中标记为final。
可以通过在类定义中class关键字之前编写修饰符final来将整个类标记为不可继承 。任何将最终类子类化的尝试都会报告为编译时错误。