问题背景:
协议抽象在 Swift 中出现,是为了对抗经典的面向对象编程中的对象继承。如果在 Objective-C 和其他面向对象语言中,通常采用“从一般到特殊”的继承方法,那么 Swift 从一开始就将协议作为实现抽象的主要方式,强调组合优于继承。
问题:
经典的继承假设了严格的层次结构:有必要通过 override 进行扩展的子类树。这限制了灵活性,导致“脆弱”的代码、复杂的重构以及基类的膨胀。此外,Swift 不支持类的多重继承——这意味着功能的重用只能通过其他机制实现。
解决方案:
协议抽象允许声明“需求集”,类型必须实现。协议可以扩展 (extension),以注入通用逻辑,使其更接近“混入”的概念:
代码示例:
protocol Drawable { func draw() } extension Drawable { func draw() { print("默认绘图") } } struct Circle: Drawable {} let c = Circle() c.draw() // 将输出 "默认绘图"
主要特点:
协议的扩展和类中的普通实现之间有什么区别?
通过扩展协议添加的默认实现仅在用户未在其类型中实现该方法的情况下生效。如果类型中显式实现了该方法,则调用的是该实现。
示例:
protocol Demo { func foo() } extension Demo { func foo() { print("默认") } } struct X: Demo { func foo() { print("自定义") } } X().foo() // "自定义"
如果以协议数据的方式调用实现协议的类型及其扩展,会发生什么?
如果协议声明了一个方法为强制性(要求),即使转换为协议类型,也会使用具体类型的实现。然而,如果在扩展中添加了新的(在协议中未声明的)属性,则该属性只能通过扩展访问,而不能通过协议类型访问。
可以将实现同一协议的不同结构体的实例存储在数组中吗?
可以——通过“存在类型”(例如,[Drawable]),可以存储异构集合:
struct Tri: Drawable { func draw() { print("三角形") } } let arr: [Drawable] = [Circle(), Tri()] arr.forEach { $0.draw() }
公司中有一个基础超类 Shape,所有图形(Circle,Square,Polygon)都从其继承。基类膨胀,因为每添加一个新图形就必须通过 override 进行维护。扩展系统变得越来越困难——每个新类型破坏 ABI,并迫使重写现有代码。
优点:
缺点:
开始使用多个协议:Drawable,Colorable,Animatable。现在每个图形都可以轻松同时变为“可动画和上色”,而不更改其他结构。新的功能通过扩展添加。
优点:
缺点: