为什么在Swift中协议优于类?

10
通过观看苹果提供的视频教程,似乎Swift是面向协议编程语言,苹果鼓励程序员使用协议而非类。 但就我个人看来,我没有看到协议的明显优势。类可以符合协议,但它们也可以继承自超类。我们可以为协议添加扩展,但我们也可以为类添加扩展。我们可以在符合协议的类中实现函数,但我们也可以在子类中覆盖函数。 我仍然困惑为什么我们需要使用协议而不是类。以及何时应该使用协议而不是类?

2
这是我的例子:如果我们有一个从Vehicle继承而来的Car和一个从BuildingMaterial继承而来的Wood,并且我们想要为CarWood添加burn方法,那么协议将是最合适的选择。 - tktsubota
1
我不确定你指的是哪个视频,但是WWDC 2015年的面向协议编程详细描述了协议的优点。 - Rob
4个回答

16

类和协议是不同的概念。协议跨越类树,将一个或多个具有不同继承关系的类连接起来。

更简单地说:

  • "class" 定义一个对象是什么。
  • "protocol" 定义了对象具有的行为。

因此,你可以有一个名为 Car 的类:

class Car {
    var bodyStyle : String
}

和一个名为Color的类:

class Color {
    var red : Int
    var green : Int
    var blue : Int
}

现在,颜色和汽车似乎完全无关,但是,假设我想轻松将它们中的任何一个转换为字符串,以便进行调试:

print(Car(...))


print(Color(...))

为了达到这个目的,Swift语言定义了协议CustomStringConvertible,因此我们可以声明一个汽车可以通过使用该协议进行打印:
extension Car : CustomStringConvertible {
    var description : String { get { return "Car: \(bodyStyle)" } }
}

还有一个颜色:

extension Color : CustomStringConvertible {
    var description : String { get { return "Color: \(red) \(green) \(blue)" } }
}

所以,在此之前,我可能需要为每个类编写一个打印方法,现在只需要一个类似于以下内容的打印方法:

func print(data:CustomStringConvertible) {
    let string = data.description
    ... bunch of code to actually print the line
}

这是可能的,因为声明一个类实现协议就是承诺我可以使用该协议的方法,知道它们已经被实现并且(大概)做了期望的事情。


1
但是在 ObjC 中,您不也能够符合多个协议吗?Swift 有什么不同呢? - mfaani

13

让我们以一个下载的例子来说明。

你有一个基类FileDownloadModel,并且有3个子类AudioFileDownloadModel、VideoFileDownloadModel和ImageDownloadModel

你有一个DownloadManager,它接收FileDownloadModel作为输入,并使用该模型的urlToDownload属性来下载文件。

后来你被告知还有一种类型的模型即将到来,但它是UserDownloadModel,是User的一个子类,而不是FileDownloadModel

现在这种情况变得很难处理,因为你需要改变很多代码来实现下载方法。

协议导向编程如何帮助你:

  1. 创建一个名为DownloadingFileProtocol的协议,并添加所需的方法和属性以用于下载文件。例如:urlToDownload、pathToSave、extension等。
  2. FileDownloadModelUserDownloadModel中实现相同的协议。其中的好处是,在UserDownloadModel中你不必改变太多的代码。你只需要实现来自DownloadingFileProtocol的方法即可。
  3. 如果又出现了一个新的实体,你就不需要改变任何代码。相反,你只需实现协议方法即可。
  4. 现在你的DownloadManager可以接收一个DownloadingFileProtocol作为输入,而不是具体的模型。此外,通过让任何模型采用该协议,现在你可以使任何模型“可下载”了。

为什么不直接创建一个DownloadingFile类,而不是协议?这样有什么区别吗?FileDownloadModel和UserDownloadModel将成为DownloadingFile类的子类。 - venndi

3
主要是类型层次结构。假设您有一个表示“GlowingRedCube”的对象,但希望将该类型用于许多通用代码,该代码关心以下事项:
- 不同的形状 - `Cube` 扩展 `Shape` - 不同的颜色 - `Red` 扩展 `Colorful` - 不同类型的照明 - `Glowing` 扩展 `Illuminated`
您可能会遇到麻烦。您可以选择一个基类并添加专业化:`GlowingRedCube` 扩展 `GlowingCube` 扩展 `Shape`,但那样你就得到了一个非常宽的类集,并且一套不灵活的东西(如果您想制作一个 `SoftRedCube`,但保留您为现有红立方体定义的任何方法,该怎么办?)
您可以只有一个 `Cube` 并使照明和形状成为属性,但是这样您将无法得到良好的编译器类型检查:如果您有一个 `Room.lightUp()` 方法并必须传递一个 `Cube`,则需要检查该类型是否包括任何照明!如果您只能传递 `Illuminated`,那么编译器在您尝试时立即停止。
协议允许您将此分离:`GlowingRedCube` 可以实现 `Illuminated` 协议、`Colorful` 协议和 `Shape` 协议。由于协议扩展,您可以包括功能的默认实现,因此您无需选择要将其附加到的层次结构级别。
struct GlowingRedCube: Shape, Colorful, Illuminated {
 // ..
}

有效地,协议允许你将行为附加到一个对象上,而不管该对象做了什么。这正是它们被用于委托和数据源协议的原因:即使你大多数情况下都将它们附加到 ViewController, 底层对象也不相关,因此你可以灵活地实现。
然而,在Swift中使用协议远不止这些基础内容: 它们非常强大,因为它们可以附加到许多不同的代码结构: 类、结构体和枚举。这使你真正能够采用面向协议的编程方式。有一段很棒的视频介绍了这种方法,来自去年的 WWDC,但首先自己尝试一些不同的对象结构,以便更好地理解问题。

1

通过协议,一个类/结构体可以被用作不同的事情。例如,String 结构体符合很多协议!

Comparable
CustomDebugStringConvertible
Equatable
ExtendedGraphemeClusterLiteralConvertible
Hashable
MirrorPathType
OutputStreamType
Streamable
StringInterpolationConvertible
StringLiteralConvertible
UnicodeScalarLiteralConvertible

这意味着String可以用作11种不同的东西!当一个方法需要以上任何一种协议作为参数时,您可以传递一个字符串。
“但我可以创建一个拥有所有协议方法的大类!”你争辩道。请记住,在Swift中,您只能从一个类继承,并且多重继承使用起来非常危险,可能会使您的代码变得超级复杂。
此外,使用协议,您可以定义类的自定义行为。Stringhashcode方法与Int的方法不同。但它们都与此函数兼容:
func doStuff(x: Hashable) {

}

这就是协议的真正奇妙之处。

最后但并非最不重要的,协议有意义。协议代表了一种“是一种”或“可以用作”的关系。当X可以用作Y时,X符合协议Y是有意义的,对吧?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接