尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Swift 继承完全指南:基类、子类化、方法重写与属性重写的深度解析

Swift 继承完全指南:基类、子类化、方法重写与属性重写的深度解析 Swift 继承完全指南基类、子类化、方法重写与属性重写的深度解析【免费下载链接】the-swift-programming-language-in-chinese中文版 Apple 官方《Swift 编程语言》项目地址: https://gitcode.com/gh_mirrors/th/the-swift-programming-language-in-chinese类的继承是 Swift 面向对象编程的基石之一也是 Swift 中区别类与其他类型结构体、枚举的基本特性。本文以 Apple 官方《Swift 编程语言》TSPL中文版 Inheritance.md 为骨架结合本仓库其他语言指南章节的交叉引用系统讲解基类定义、子类化、override重写、super访问父类实现、属性观察器重写以及final防止重写等核心机制并附上完整可运行的代码示例。读完本文你将能够熟练设计类层次结构安全地重写方法与属性并在需要时锁定类的行为。继承允许一个类从另一个类继承方法、属性和其他特性。当一个类从另一个类继承时继承的类被称为子类而被它继承的类被称为父类。Swift 中的类可以调用和访问属于其父类的方法、属性和下标并且可以通过重写这些方法、属性和下标来优化或修改它们的行为。Swift 编译器会检查重写定义是否与父类定义相匹配从而帮助确保你的重写是正确的。类还可以为继承的属性添加属性观察器以便在属性值发生变化时得到通知——无论该属性最初是定义为存储属性还是计算属性。定义基类如果一个类没有继承其他类那它就是一个基类。注意Swift 中没有统一的基类所有类的起源都是平等的。你不指定父类的类会自动成为基类。下面的示例定义了一个名为Vehicle的基类。这个基类定义了一个名为currentSpeed的存储属性默认值为0.0推断出属性类型为Double。currentSpeed属性的值被一个只读计算String属性description用于创建车辆的描述。Vehicle基类还定义了一个名为makeNoise()的方法。这个方法对于基类Vehicle的实例不会做任何事情但稍后会被Vehicle的子类自定义class Vehicle { var currentSpeed 0.0 var description: String { return traveling at \(currentSpeed) miles per hour } func makeNoise() { // 不做任何事情 - 不是任何一辆车都会发出噪音。 } }你可以使用初始化语法创建一个新的Vehicle实例写作类型名后跟一对空括号let someVehicle Vehicle()创建了一个新的Vehicle实例后你可以访问它的description属性来打印车辆当前速度的描述print(Vehicle: \(someVehicle.description)) // Vehicle: traveling at 0.0 miles per hourVehicle类定义了任意车辆的通用特性但本身并不太有用。要使其更有用你需要对其进行完善以描述更具体的车辆类型。子类化子类化是基于现有类创建新类的行为。子类继承现有类的特性然后你可以对其进行完善也可以向子类添加新的特性。要指示子类有一个父类请在父类名前写子类名中间用冒号分隔class SomeSubclass: SomeSuperclass { // 子类定义在这里 }示例继承并扩展 Vehicle下面的示例定义了一个名为Bicycle的子类它的父类是Vehicleclass Bicycle: Vehicle { var hasBasket false }新的Bicycle类自动获得了Vehicle的所有特性例如它的currentSpeed和description属性以及makeNoise()方法。除此之外Bicycle类还定义了一个新的存储属性hasBasket默认值为false推断该属性的类型为Bool。默认情况下你创建的任何新Bicycle实例都默认没有篮子。在创建实例后你可以将特定Bicycle实例的hasBasket属性设置为truelet 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的子类用于双人自行车称为 tandemclass Tandem: Bicycle { var currentNumberOfPassengers 0 }Tandem继承了Bicycle的所有属性和方法而Bicycle又继承了Vehicle的所有属性和方法。Tandem子类还添加了一个名为currentNumberOfPassengers的新存储属性默认值为0。如果你创建一个Tandem实例你可以使用它的任何新属性和继承的属性并查询它从Vehicle继承的只读属性descriptionlet tandem Tandem() tandem.hasBasket true tandem.currentNumberOfPassengers 2 tandem.currentSpeed 22.0 print(Tandem: \(tandem.description)) // Tandem: traveling at 22.0 miles per hour至此我们已经形成了一个三层的类层次结构Vehicle→Bicycle→Tandem每一层都在上一层的特性基础上叠加新的存储属性。重写子类可以提供自己的自定义实现来覆盖它将从父类继承的实例方法、类型方法、实例属性、类型属性或下标。这被称为重写。要重写将被继承的特性你需要在重写定义前加上override关键字。这样做可以明确你打算提供重写而不是由于疏忽而提供了相同的定义。无意间的重写可能会导致意外行为任何没有override关键字的重写在编译代码时都会被诊断为错误。override关键字还会提示 Swift 编译器检查你的重写类的父类或其父类之一是否有与提供的重写定义相匹配的声明。这个检查可以确保你的重写定义是正确的——如果你拼错了方法名、写错了参数类型或返回值类型编译器会直接报错而不是在运行时才发现问题。访问父类方法、属性和下标当你为子类提供方法、属性或下标重写时有时使用现有父类实现作为重写的一部分是很有用的。例如你可以改进现有实现的行为或在现有继承的变量中存储修改后的值。在适当的情况下你可以使用super前缀来访问父类的方法、属性或下标一个被重写的名为someMethod()的方法可以在重写方法实现中通过调用super.someMethod()来调用父类版本的someMethod()。一个被重写的名为someProperty的属性可以在重写的 getter 或 setter 实现中通过super.someProperty来访问父类版本的someProperty。一个被重写的针对someIndex的下标可以在重写的下标实现中通过super[someIndex]来访问同一下标的父类版本。重写方法你可以重写继承的实例或类型方法以在子类中提供该方法的定制或替代实现。Swift 的类型方法使用static关键字声明类上也可以用class func同样支持重写相关语法基础可以参考 Methods.md 中的类型方法一节。下面的示例定义了一个名为Train的新Vehicle子类它重写了从Vehicle继承的makeNoise()方法class Train: Vehicle { override func makeNoise() { print(Choo Choo) } }如果你创建一个新的Train实例并调用它的makeNoise()方法你可以看到调用了Train子类版本的方法let train Train() train.makeNoise() // 打印 Choo Choo。重写属性你可以重写继承的实例或类型属性为该属性提供自己的自定义 getter 和 setter或添加属性观察器以使重写的属性能够观察底层属性值的变化。重写属性 Getter 和 Setter你可以为任何继承的属性提供自定义 getter如果需要的话还有 setter无论继承的属性在源码中是作为存储属性还是计算属性实现的。子类不知道继承属性的存储或计算性质它只知道继承的属性有一个特定的名称和类型。你必须始终声明要重写的属性的名称和类型以便编译器检查你的重写与父类中具有相同名称和类型的属性相匹配。你可以通过在子类属性重写中提供 getter 和 setter 来将继承的只读属性表示为可读写属性。但是你不能将继承的可读写属性声明为只读属性。注意如果你在属性重写中提供了 setter你也必须为该重写提供 getter。如果你不想在重写的 getter 中修改继承属性的值你可以简单地通过从 getter 返回super.someProperty来传递继承的值其中someProperty是正在重写的属性的名称。下面的示例定义了一个名为Car的新类它是Vehicle的子类。Car类引入了一个名为gear的新存储属性默认整数值为1。Car类还重写了它从Vehicle继承的description属性以提供包含当前档位的自定义描述class Car: Vehicle { var gear 1 override var description: String { return super.description in gear \(gear) } }description属性的重写首先调用super.description它返回Vehicle类的description属性。然后Car类的description版本在此描述的末尾添加了一些额外文本以提供有关当前档位的信息。注意这里的super语法在属性重写中super不是通过super()调用而是通过super.propertyName访问父类版本的属性。如果你创建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。重写属性观察器你可以使用属性重写的方式为继承的属性添加属性观察器。这样无论该属性最初是如何实现的存储属性或计算属性你都能够在继承属性的值发生变化时得到通知。属性观察器机制的完整说明参见 Properties.md 的属性观察器一节其中明确指出属性观察器可以添加在自定义的存储属性、继承的存储属性和继承的计算属性上每次属性值被设置时无论新值是否与当前值相同观察器都会被调用。注意你无法为继承的常量存储属性或继承的只读计算属性添加属性观察器。这些属性的值无法被修改所以在重写时提供willSet或didSet实现是不合适的。另请注意你不能为同一属性提供重写的 setter 和重写的属性观察器。如果你想观察属性值的变化并且已经为该属性提供了自定义 setter你可以简单地在自定义 setter 中观察任何值的变化。以下示例创建了一个名为AutomaticCar的新类它继承自Car类。AutomaticCar类表示一辆带有自动变速箱的汽车根据当前速度自动选择合适的挡位class AutomaticCar: Car { override var currentSpeed: Double { didSet { gear Int(currentSpeed / 10.0) 1 } } }注意currentSpeed在Vehicle中是一个存储属性而在AutomaticCar中我们通过重写为它添加了didSet观察器这正是无论最初是存储属性还是计算属性都可以为任何继承属性添加属性观察器的体现。每当你设置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这里还可以观察到 Swift 动态派发的特点description的计算发生在基类Vehicle中但由于Car重写了description、AutomaticCar重写了currentSpeed并联动修改gear最终输出的描述同时体现了多层重写的叠加效果——AutomaticCar实例继承了Vehicle的速度语义、Car的描述文本与gear属性以及自动换挡的联动逻辑。重写与访问控制继承和重写还与访问控制紧密相关。根据 AccessControl.md 的说明open仅适用于类及类的成员它与public的不同之处在于open允许模块外的代码进行继承和重写。将类显式指定为open表明你已考虑到其他模块的代码将该类用作父类的影响并为此相应地设计了类的代码。对于同一模块中定义的类你可以重写在上下文中可访问的任意类成员方法、属性、构造器或下标。对于在其他模块中定义的类你可以重写访问级别为open的任意类成员。通过重写可以给子类的成员提供更高的访问级别子类可以重写父类中较低访问级别如fileprivate的成员并将其提升为更高的访问级别如internal。防止重写你可以通过将其标记为final来防止方法、属性或下标被重写。在方法、属性或下标的引入关键字前写final修饰符例如final var、final func、final class func和final subscript。任何尝试在子类中重写 final 方法、属性或下标的行为都会在编译时报错。你在扩展中添加到类的方法、属性或下标也可以在扩展的定义中标记为 final相关语法参见 Extensions.md。下面的测试示例展示了编译器对 final 成员的强制检查对应原文档finalPreventsOverriding测试class C { final var someVar 0 final func someFunction() { print(In someFunction) } } class D : C { override var someVar: Int { get { return 1 } set {} } override func someFunction() { print(In overridden someFunction) } } // 编译错误 // error: property overrides a final property // error: instance method overrides a final instance method防止类被继承你可以通过在类定义中在class关键字前写final修饰符即final class来将整个类标记为 final。任何尝试子类化 final 类的行为都会在编译时报错final class C { var someVar 0 func someFunction() { print(In someFunction) } } class D : C { override var someVar: Int { get { return 1 } set {} } } // 编译错误 // error: property overrides a final property // error: instance method overrides a final instance method // error: inheritance from a final class C注意上面的例子揭示了 final 的一个细微行为D中的属性重写和继承错误都会报告因为一旦C是 final 类D: C本身就是非法的其内部对C成员的任何重写也就同样非法。实战要点总结围绕本章内容整理出以下可以直接应用于项目中的要点继承只适用于类继承是 Swift 中区别类与其他类型结构体、枚举的基本特性。结构体和枚举不支持继承但可以遵循协议来获得类似的多态能力。基类不需要显式声明Swift 没有统一的根类任何未指定父类的类自动成为基类。重写必须使用override关键字缺少override的同名定义会被编译器诊断为错误防止无意间的重写。善用super组合父类实现在重写方法中通过super.someMethod()、在属性重写中通过super.someProperty、在下标重写中通过super[someIndex]复用父类逻辑这是扩展而非替换父类行为的标准模式。属性重写的两条对称规则只读属性可以被重写为可读写但可读写属性不能被重写为只读提供了 setter 就必须同时提供 getter可用super.someProperty透传。属性观察器的边界可以为继承的存储属性和计算属性添加观察器但不能为继承的常量存储属性、只读计算属性添加观察器也不能在同一属性上同时提供 setter 重写和属性观察器重写。用final锁定设计意图对不打算被重写的成员或类使用final修饰符可以让编译器在编译期拦截误用跨模块开放继承与重写的能力则需要open访问级别。结语继承、重写与final共同构成了 Swift 类体系的核心控制手段继承实现代码复用与类型层次override配合super实现行为的定制与扩展final则用于锁定不可变的设计边界。掌握本章内容后你不仅能够构建清晰的类层次结构还能借助编译器的检查机制写出更加安全、可维护的面向对象代码。本章所有示例均来自本仓库的 Inheritance.md并可结合 Properties.md属性观察器、AccessControl.mdopen与重写、Methods.md类型方法等章节进行系统学习。【免费下载链接】the-swift-programming-language-in-chinese中文版 Apple 官方《Swift 编程语言》项目地址: https://gitcode.com/gh_mirrors/th/the-swift-programming-language-in-chinese创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表