编程Swift中级开发者

什么是Swift中的协议组合,它是如何工作的,为什么需要它?使用多个协议时有什么潜在问题?

用 Hintsage AI 助手通过面试

答案。

在Swift中,许多面向对象编程语言的经验被概括和完善,通过组合(而不仅仅是继承)协议的能力。协议组合允许声明变量、函数参数或泛型,要求同时符合多个协议。这个机制在处理具有多个合同(接口)行为的对象时非常有用,同时灵活地避免多重继承的缺点。协议组合解决的问题是需要表达“对象必须满足一组要求”,而不仅仅是一个。

在Swift的实现中使用了特殊的语法:协议的组合用&符号(ampersand)表示,例如,protocolA & protocolB。在内部进行运行时检查(例如,在类型转换和泛型上下文中)。这最小化了类型的数量,并灵活地实现了“职责分离”模式。

代码示例:

protocol Drawable { func draw() } protocol Movable { func move() } struct Sprite: Drawable, Movable { func draw() { print("Sprite draws") } func move() { print("Sprite moves") } } func animate(object: Drawable & Movable) { object.draw() object.move() } let s = Sprite() animate(object: s)

关键特点:

  • 灵活地表达行为的组合,而无需继承层次结构
  • 确保同时满足所有合同
  • 兼容泛型参数和类型别名

误导性问题。

可以创建类型为仅有protocolA & protocolB的变量,而不依赖于特定的结构或类吗?

是的,可以声明一个变量同时符合多个协议,例如:

var obj: protocolA & protocolB

但需要注意:如果组合中至少有一个协议限制为类类型(protocol: AnyObject),则这些变量只能引用对象(而不是值类型)。

可以将类类型包含在组合中,例如SomeClass & Drawable吗?

可以,但有细微之处:SomeClass & Protocol类型的组合要求值必须是该类(或其子类)的实例,并实现协议。这种方法用于限制泛型类型。

可以在协议扩展中将协议组合用作关联类型吗?

可以,但有一些限制:不能将associatedtype声明为组合,但可以在扩展中使用where来限制协议组合,例如,仅适用于符合多个协议的类型的扩展。

常见错误和反模式

  • 使用八到九个协议的组合:这是架构过载和糟糕责任划分的迹象
  • 将值类型(结构体)强制转换为有AnyObject限制的协议组合变量始终会导致错误
  • 在应用程序的不同部分使用相同的组合而没有类型别名:会降低可读性

生活中的例子

负面案例

在项目中实现了5个相似的协议 — Drawable, Movable, Resizable, Colorable, Animatable。处处使用组合 Drawable & Movable & Resizable & Colorable & Animatable。常见错误伴随着复杂的bug,因为某些实体未实现其中一个合同。

优点:

  • 不需要深层次的继承
  • 容易添加或删除功能

缺点:

  • 难以跟踪不符合
  • 测试复杂
  • 声明可读性差

正面案例

与复杂的组合相比,提炼出两个主要协议(例如Actor和Viewable),为组合"DynamicEntity"创建了类型别名,并在各处使用它。明确划分了职责。

优点:

  • 代码更容易阅读和维护
  • 测试清楚地突出DynamicEntity的行为
  • 快速修改要求列表

缺点:

  • 需要重新思考架构
  • 有时需要拆分现有类以满足要求