想一次理解 OOP 多态,从重写到协议,却越看越糊涂吗?
重载、重写和协议看起来很相似,很容易混淆。
先说结论:多态就是“同名调用会根据目标产生不同的行为”。重写通过继承实现,而协议通过约定实现。
本文会逐步讲清楚这三个概念之间是如何关联的。
多态到底是什么?
多态(Polymorphism)在希腊语中意为“多种(poly)形态(morph)”。
简单来说,即使发送相同的消息,不同对象也会做出不同响应。
例如,同样让狗和猫“发出声音”,狗会汪汪叫,猫会喵喵叫。
调用方只需要调用소리내()。具体产生什么内容,由每个对象自行决定。
多态的核心是“调用方不需要知道”。
无论传入什么,都可以用同一份代码处理。
这让代码更加灵活。即使添加新动物,也不需要修改调用代码。
重载和重写有什么区别?
它们的名称相似,这是最容易混淆的地方。
重载是指创建多个同名函数,只改变参数。
重写是指子类重新定义并覆盖父类创建的函数。
只听文字不容易理解,我们直接看代码。
class Animal {
func sound() { print("...") }
}
class Dog: Animal {
override func sound() { print("汪汪") } // 重写
}
let a: Animal = Dog()
a.sound()
// 输出:汪汪
有趣的是,虽然a的类型是Animal,却会发出Dog的声音。
这就是在运行时查看实际对象后决定的多态。
重载则有所不同。要调用哪个函数,会在编译时确定。
func add(_ a: Int, _ b: Int) -> Int { a + b }
func add(_ a: String, _ b: String) -> String { a + b }
print(add(1, 2)) // 输出: 3
print(add("甲", "乙")) // 输出:甲乙
因此,重载也叫静态多态,重写则叫动态多态。
为什么需要协议?
只靠继承也能实现多态,那为什么还需要协议?
继承表示“is-a”关系,因此只能有一个父类。猫很难既是动物,又同时属于另一个层级。
协议采用“只要遵守这份约定,什么类型都可以”的方式,因此自由得多。
protocol Soundable {
func sound()
}
struct Cat: Soundable {
func sound() { print("喵喵") }
}
struct Car: Soundable {
func sound() { print("嘀嘀") }
}
let things: [Soundable] = [Cat(), Car()]
things.forEach { $0.sound() }
// 输出:喵喵
// 输出:嘀嘀
猫和汽车没有任何继承关系,但仍然可以放在同一个数组中,以相同方式处理。
不依赖继承关系也能获得多态,这就是协议的力量。
这也是 Swift 等语言优先使用协议而不是继承的原因。
什么时候使用继承,什么时候使用协议
我把实际工作中的判断标准整理成了表格。
| 情况 | 判断 |
|---|---|
| 想复用公共代码 | 继承(重写) |
| 没有关系,但想保证相同的行为 | 协议 |
| 想同时赋予多种特性 | 协议(可以同时采用多个) |
| 想让值类型(struct)具备多态 | 协议 |
只要记住几个简单原则即可。
- 需要复用大量父类实现时,使用继承
- 想定义“能够做什么”时,使用协议
- 如果关注可扩展性,优先考虑协议
面试时会这样问
Q. 请说明重载和重写的区别。
重载是在同一个名称下定义不同参数,在编译时确定。重写是子类重新定义继承的方法,在运行时根据实际对象确定。因此,前者称为静态多态,后者称为动态多态。
Q. 使用协议代替继承有什么好处?
继承只能有一个父类,不能用于值类型;协议则可以同时采用多个,也可以应用于 struct。没有关系的类型也能通过同一约定归为一组并统一处理,因此更有利于扩展。
多态起初可能很抽象,但只要抓住“调用方不需要知道”这句话,其他内容就会顺畅起来。
把今天学到的重写和协议各用代码实践一次,就会真正掌握。加油!

