Swift 5.x - 继承(中文文档)

2,619 阅读10分钟

引言

继续学习Swift文档,从上一章节:下标,我们学习了Swift下标相关的内容,如下标语法、下标用法、下标选项和类型下标等这些内容。现在,我们学习Swift的继承相关的内容。由于篇幅较长,这里分篇来记录,接下来,Fighting!

熟悉这一章节的朋友可以直接跳过下一章节:初始化

继承

一个类可以从另一个类继承方法,属性和其他特征。 当一个类从另一个类继承时,继承的类称为子类,而其继承的类称为其父类。 继承是一种基本行为,可将Swift中的类与其他类型区分开。

Swift中的类可以调用和访问属于其父类的方法,属性和下标,并可以提供这些方法,重写属性和下标以完善或修改其行为。 Swift通过检查覆盖定义是否具有匹配的父类定义来帮助确保覆盖是正确的。

类还可以将属性观察器添加到继承的属性中,以便在属性值更改时得到通知。 可以将属性观察器添加到任何属性,而不管其最初是定义为存储属性还是计算属性。

1 定义基类

任何不从另一个类继承的类都称为基类。

注意
Swift类不能从通用基类继承。 您没有指定父类而定义的类将自动成为基类供您构建。

下面的示例定义了一个称为Vehicle的基类。 该基类定义了一个称为currentSpeed的存储属性,默认值为0.0(推断属性类型为Double)。 currentSpeed属性的值由一个称为description的只读计算出的String属性用于创建车辆的描述。

Vehicle基类还定义了一个称为makeNoise的方法。 此方法实际上不对基础Vehicle实例执行任何操作,但稍后将由Vehicle的子类自定义:

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
    }
}

使用初始化器语法创建一个Vehicle的新实例,该实例被写为类型名称,后跟空括号:

let someVehicle = Vehicle()

创建新的Vehicle实例后,您可以访问其description属性,以打印出人类可以理解的车辆当前速度的描述:

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

Vehicle类定义了任意车辆的共同特征,但其本身并没有太多用途。 为了使其更有用,您需要对其进行改进以描述更多特定类型的车辆。

2 子类

子类化是在现有类的基础上建立新类的行为。 子类继承现有类的特征,然后可以对其进行优化。 您还可以向子类添加新特征。

要表明子类具有父类,请在父类名称之前写上子类名称,并用冒号分隔:

class SomeSubclass: SomeSuperclass {
    // subclass definition goes here
}

下面的示例定义了一个名为Bicycle的子类,并带有Vehicle的父类:

class Bicycle: Vehicle {
    var hasBasket = false
}

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

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

默认情况下,您创建的任何新Bicycle实例都没有篮子。 创建特定的Bicycle实例后,可以将hasBasket属性设置为true:

let bicycle = Bicycle()
bicycle.hasBasket = true

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

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

子类本身可以被子类化。 下一个示例为称为“双人”的两人座自行车创建Bicycle的子类:

class Tandem: Bicycle {
    var currentNumberOfPassengers = 0
}

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

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

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

3 重写

子类可以提供其自己的实例方法,类型方法,实例属性,类型属性或下标的自定义实现,否则该实例方法将从超类继承。 这称为重写。

要重写原本会被继承的特征,请在您的重写定义前添加override关键字。 这样做可以明确您打算提供替代,并且没有错误地提供匹配的定义。 偶然的重写可能会导致意外的行为,并且在编译代码时,任何不带有override关键字的替代都会被诊断为错误。

Override关键字还会提示Swift编译器检查您的重写类的父类(或其父类之一)是否具有与您为该替代提供的声明相匹配的声明。 此检查可确保您的重写定义正确。

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

当为子类提供方法,属性或下标替代时,将现有的超类实现用作替代的一部分有时会很有用。 例如,您可以优化该现有实现的行为,或者将修改后的值存储在现有的继承变量中。

在适当的情况下,您可以使用super前缀访问方法,属性或下标的超类版本:

  • 名称为someMethod()的重写方法可以通过在重写方法实现中调用super.someMethod()来调用someMethod()的父类方法。
  • 重写的名称为someProperty的属性可以在重写的getter或setter实现中以super.someProperty的方式访问someProperty的父类属性。
  • someIndex的重写下标可以从重写下标实现中访问与super [someIndex]相同的下标的父类下标。

3.2 重写方法

您可以重写继承的实例或类型方法,以在子类中提供该方法的定制或替代实现。

以下示例定义了一个名为Train的Vehicle的新子类,该子类重写Train从Vehicle继承的makeNoise()方法:

class Train: Vehicle {
    override func makeNoise() {
        print("Choo Choo")
    }
}

如果创建一个Train的新实例并调用其makeNoise()方法,则可以看到该方法的Train子类版本称为:

let train = Train()
train.makeNoise()
// Prints "Choo Choo"

3.3 重写属性

您可以重写继承的实例或类型属性,以为该属性提供自己的自定义getter和setter,或添加属性观察器以使重写的属性能够在基础属性值更改时进行观察。

重写属性的Getters和Setters 您可以提供一个自定义getter(如果合适的话,可以使用setter)来重写任何继承的属性,而不管该继承的属性是在源上实现为存储属性还是计算属性。 子类不知道继承属性的存储或计算性质,它仅知道继承属性具有特定名称和类型。 您必须始终声明要重写的属性的名称和类型,以使编译器能够检查您的重写是否与具有相同名称和类型的父类属性匹配。

通过在子类属性重写中同时提供getter和setter,可以将继承的只读属性呈现为读写属性。 但是,您不能将继承的读写属性表示为只读属性。

注意
如果在属性替代中提供了一个setter,则还必须为该替代提供一个getter。 如果您不想在重写的getter中修改继承的属性的值,则可以通过从getter返回super.someProperty来传递继承的值,其中someProperty是您要重写的属性的名称。

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

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

对description属性的重写是通过调用super.description开始的,super.description返回Vehicle类的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

重写属性监听 您可以使用属性重写将属性观察器添加到继承的属性。 这使您可以在继承属性的值更改时得到通知,而无论该属性最初是如何实现的。 有关属性观察器的更多信息,请参见Property Observers

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

还要注意,您不能为同一属性同时提供覆盖设置器和覆盖属性观察器。 如果您想观察属性值的变化,并且已经在为该属性提供自定义设置器,则可以简单地观察自定义设置器中的任何值更改。

下面的示例定义一个名为AutomaticCar的新类,它是Car的子类。 AutomaticCar类代表具有自动变速箱的汽车,该变速箱会根据当前速度自动选择要使用的档位:

class AutomaticCar: Car {
    override var currentSpeed: Double {
        didSet {
            gear = Int(currentSpeed / 10.0) + 1
        }
    }
}

每当您设置AutomaticCar实例的currentSpeed属性时,该属性的didSet观察器都会将该实例的gear属性设置为新速度的适当齿轮。 具体来说,属性观察者选择的齿轮是新的currentSpeed值除以10,四舍五入到最接近的整数加1。35.0的速度产生4的齿轮:

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

4 防止重写

您可以通过将方法,属性或下标标记为final来防止其被重写。 为此,可以在方法,属性或下标的Introductionr关键字(例如final var,final func,final class func和final下标)之前编写final修饰符。

尝试重写子类中的最终方法,属性或下标的任何尝试都将报告为编译时错误。 您在扩展程序的类中添加的方法,属性或下标也可以在扩展程序的定义中标记为final。

您可以通过在类定义中的class关键字之前编写final修饰符,将整个类标记为(final class)。 任何尝试将最终类作为子类的尝试都将报告为编译时错误。

总结

这一章节主要讲的是:

  • 继承的作用:可以继承父类定义的属性、方法和下标,以扩展自定义的功能。
  • 没有从父类继承的类都是基类,在基类里面定义一些基本的属性个方法,以提供子类来扩展。
  • 使用override关键词修饰父类的属性或方法来重写父类里面实现的功能。可以重写属性的Getters和Setters、属性监听。
  • 使用final关键词修饰可以防止重写。

好了,有收获的朋友麻烦给个鼓励哦,谢谢啦~

上一章节:下标

下一章节:初始化

参考文档: Swift - Inheritance