首页 / Swift 编程语言教程 / 多态

Swift 编程语言教程

多态

本教程共 93 篇 · 第 63 篇 · 更新于 2026-08-08 · 约 8 分钟阅读

SwiftSwift 编程语言教程多态动态派发父类引用类型转换override

本节目标:学完你理解父类类型的变量可以装子类实例,调用被重写的方法时跑的是真实子类的版本,并会用类型转换取回子类专属能力。

多态(polymorphism)是面向对象里最常被吹捧、也最实用的特性之一。名字听着唬人,核心就一句话:同一个父类类型的引用,可以指向不同的子类实例;当你通过这个引用去调用一个被重写的方法时,Swift 会在运行时自动选对”真正该执行的那个版本”。这一章把它讲明白,并配上类型转换收尾。

1-1 父类引用能装子类对象

继承链上,子类”是一种”父类。比如 BicycleVehicle,所以凡是要求 Vehicle 类型的地方,你都能塞一个 Bicycle 进去。这就是多态的起点。

class Vehicle {
    func makeNoise() { }
}
class Train: Vehicle {
    override func makeNoise() { print("呜——") }
}
class Car: Vehicle {
    override func makeNoise() { print("嘀嘀") }
}

let train: Vehicle = Train()
let car: Vehicle = Car()

注意 traincar 的声明类型都是 Vehicle,但它们实际指向 TrainCar 实例。编译器只看得到”它是个 Vehicle”,至于肚子里装的是哪种车,要等运行才知道。

1-2 同一调用,不同表现

多态的威力在”统一调用、各自表现”。你把不同子类的实例放进一个 Vehicle 数组,然后遍历调用 makeNoise()

let vehicles: [Vehicle] = [Train(), Car(), Train()]
for v in vehicles {
    v.makeNoise()
}
// 呜—— / 嘀嘀 / 呜——

数组里每个元素类型都是 Vehicle,但循环里那句 v.makeNoise() 会根据 v 真正指向的实例,分别跑到 TrainCar 的重写版本。这种”同一行代码,因对象不同而行为不同”的现象,就是多态。它让你写出不依赖具体类型的通用逻辑:遍历、批量处理、统一接口,不必为每种车写一套 if 判断。

底层机制叫动态派发(dynamic dispatch):Swift 在运行时查表,找到实例真实类型对应的方法实现再调用。这和第 59 章”实例方法隐式访问成员”是同一个体系,只不过跨了继承层次。

1-3 为什么这比 if 判断优雅

没有多态时,你大概会写成这样:

// 假设不用多态的笨办法
if 是火车 { 呜—— } else if 是汽车 { 嘀嘀 }

每加一种新车,就得回头改这段判断,违反”对扩展开放、对修改封闭”的原则。有了多态,新增子类只需自己重写 makeNoise(),调用方一行都不用动。代码更松耦合,也更好维护。这也是为什么面向对象设计常把”共性”提到父类、把”差异”留给子类重写。

1-4 取回子类能力:类型转换

多态带来的小代价是:当你只有 Vehicle 类型时,访问不到子类独有的成员。比如 Cargear 属性,Vehicle 没有,直接 vehicle.gear 会编译报错——编译器不知道这个 vehicle 是不是 Car。

这时用类型转换(type casting)把类型”认回来”。Swift 提供三个运算符:is 判断是不是某类型,as? 尝试转换(失败返回 nil),as! 强制转换(失败会崩溃,慎用)。

for v in vehicles {
    if let car = v as? Car {
        print("这是汽车,挡位 \(car.gear)")
    } else if v is Train {
        print("这是火车")
    }
}

v as? Car 尝试把 v 当 Car 用,成功就进 if 并拿到真正的 Car 实例,从而访问 gearv is Train 只做判断不转换,适合只需区分类型的场景。类型转换让你在”统一用父类”和”按需取子类能力”之间自由切换。

1-5 多态成立的三个前提

回顾一下,多态不是凭空来的,它依赖前面几章的知识拼图:

第一,得有继承(第 61 章),子类”是一种”父类,才能塞进父类类型的容器。第二,得有重写(第 62 章),子类提供自己的方法版本,调用才有”不同表现”可言。第三,父类的方法或属性得是允许被动态派发的(默认实例方法就是),这样运行时才能选对版本。三者缺一,多态就立不起来。

1-6 一个常见误区

初学者容易把”重载”(同名的多个函数,参数不同,第 33 章)和”重写/多态”搞混。重载是编译期根据参数决定调哪个;多态是运行期根据对象真实类型决定调哪个。两者发生在不同阶段,解决的问题也不同。记住:多态一定跨继承、一定涉及 override、一定是运行期派发。

1-7 小结

多态 = 父类引用指向子类对象 + 调用被重写方法时动态派发到真实子类实现。它让你的代码面向”父类接口”编程,新增子类无需改动调用方。需要子类专属能力时,用 is/as?/as! 做类型转换取回。把继承、重写、多态这三章连起来看,面向对象的骨架就完整了。

1-8 协议也能带来多态

除了继承,协议(protocol,第 67 章)也能实现多态。你可以让不同类型遵循同一个协议,然后把变量声明为该协议类型,调用协议要求的方法时同样会动态派发到具体类型的实现。这种”面向协议的多态”比继承更松耦合,因为类型之间不需要是父子关系,只要都遵守同一套约定即可。Swift 官方甚至提倡用协议和值类型来组织多态,而不是动辄上继承。

1-9 多态与异构集合

多态最常见的落地点就是异构集合。比如一个 [Vehicle] 数组里混着装 CarTrainBicycle,遍历时统一调 makeNoise(),各跑各的版本。没有多态,你就得用一堆 if 判断类型。字典、函数参数同理:把参数类型写成父类或协议,调用方就能传任意具体子类,代码立刻通用。这正是多态的工程价值——让一段逻辑适配多种类型,新增类型时调用方零改动。

1-10 别为多态而多态

也要提醒:不是见到”多种类型”就该上多态。如果各类型行为差异巨大、几乎没有公共接口,硬抽父类反而别扭。多态适合”外表一致、内涵不同”的场景。初学者容易过度设计,给明明独立的几个类型硬造继承关系。记住:先有真实的共性,再谈多态;为了”显得面向对象”而造出来的继承,往往后面要还债。