在Swift中,当一个协议有一个初始化器时,为什么我不能实例化这个协议?

6

我知道通常情况下我不能实例化一个协议。 但是如果我在协议中包含一个初始化器,那么编译器肯定知道当协议稍后被结构体或类使用时,它将有一个可以使用的init吧? 我的代码如下,其中包含以下行:

protocol Solution {
  var answer: String { get }
}

protocol Problem {
  var pose: String { get }
}

protocol SolvableProblem: Problem {
  func solve() -> Solution?
}

protocol ProblemGenerator {
  func next() -> SolvableProblem
}

protocol Puzzle {
  var problem: Problem { get }
  var solution: Solution { get }

  init(problem: Problem, solution: Solution)
}

protocol PuzzleGenerator {
  func next() -> Puzzle
}

protocol FindBySolvePuzzleGenerator: PuzzleGenerator {
  var problemGenerator: ProblemGenerator { get }
}

extension FindBySolvePuzzleGenerator {
  func next() -> Puzzle {
    while true {
      let problem = problemGenerator.next()
      if let solution = problem.solve() {
        return Puzzle(problem: problem, solution: solution)
      }
    }
  }
}

这行代码:

return Puzzle(problem: problem, solution: solution)

出现错误:协议类型“Puzzle”无法实例化。


如果没有绕过实例化问题的方法,那么我是否有其他方法从协议扩展返回Puzzle? - Cortado-J
但是如果我在协议中包含一个初始化器,那么编译器肯定知道当协议稍后被结构体或类使用时。不是这样的;可能相关:https://stackoverflow.com/questions/41074354/protocol-type-cannot-be-instantiated - Ahmad F
5个回答

8
想象一下协议就像形容词。例如 Movable 表示你可以将其移动Red 表示它的颜色 = "红色"...但是它们并没有说明是什么。你需要一个名词。一个红色、可移动的汽车。即使缺少细节,你也可以实例化一辆汽车。但你无法实例化一个红色。

谢谢,我明白了。 那么如果这意味着我无法让FindBySolvePuzzleGenerator实例化一个Puzzle,是否还有其他实现方式呢? 我知道我将要实现FindBySolvePuzzleGenerator,并且它将生成Puzzles。我也知道它将如何实现,而我正试图在扩展中描述它,但不知道如何做到。 - Cortado-J
你认为为什么Puzzle(谜题)需要首先成为一个协议呢?(或者说,你创建的大多数实体都需要成为协议)。确保你理解了“is-a”和“has-a”的关系,并确保你按照名称命名事物。举个例子,我期望solution(解决方案)是Puzzle(谜题)类中的一个字段;可能是一个名为Solvable(可解决的)或HasSolution(有解决方案的)的协议中的一个字段。Solution(解决方案)不应该是一个协议。question(问题)绝对应该直接成为Puzzle(谜题)类的成员,因为如果它不提出问题,那么它就不是一个谜题。将其转换为协议毫无意义。 - Amadan
感谢@Amadan,有用的问题。 我的想法是,我的谜题可以由非常不同的问题和解决方案组成。 有些问题可能是一串文本,有些可能是图像,答案也可能是可变的 - 所以我试图保持尽可能通用,以便未来的变化。 但是...这就是为什么你的问题很有用 - 即使我的问题和解决方案可能会有所不同,一个谜题(至少在我这里的思考中)始终包含一个问题和一个解决方案,因此可以是一个结构体而不是协议 - 我会尝试一下...并思考你的其他评论。 - Cortado-J
如果您有不同的问题,请创建一个抽象类Question,并使用不同类型的子类进行继承。这不是协议的问题。如果有某些共享特定类型(例如DrawableSelectableFromAList)的问题类,实现它们可能是一个好的协议(如果有意义的话)。 (您可能还想在这些子类中包括answer,因为不同类型的问题有不同类型的答案。) - Amadan
通过将Puzzle从协议更改为结构体,问题就解决了。 我想我有点过于急于将所有东西都转换为协议了。 我会仔细检查,但我认为我的所有其他类型都需要保持为协议。再次感谢@Amadan的帮助。 - Cortado-J

3
我知道我做不到 - 我只是想了解为什么编译器不能做到?
因为在Swift中,协议代表着抽象机制。当涉及到抽象时,你可以将其视为模板,我们不必关心它的行为方式或属性是什么;因此,能够从它创建对象是没有意义的。
作为一个现实世界的例子,考虑一下我刚才说的“桌子”(作为一个抽象层次),我相当确定你会明白我在说什么!尽管我们没有提及它的细节(比如它的材料或它有多少条腿...);但如果我说“给我创建一张桌子”(实例化一个对象),你就必须问我规格!这就是为什么编译器不允许你直接从协议创建对象的原因。这就是使事物抽象化的目的。
另外,查看:为什么不能创建抽象类的对象?可能会有所帮助。

3
但如果我在协议中包含初始化程序,那么编译器肯定知道当协议稍后被结构体或类使用时,它将具有可用的 init 吗?
协议必须由类采用,并且可能有数十个不同的类都采用了您的 Puzzle 协议。编译器不知道要实例化其中哪个类。
协议赋予我们组合接口的能力,而不会出现多重继承的复杂性。在像 C++ 这样的多重继承语言中,您不得不处理一个单独的类 D 可能继承自两个其他类 B 和 C 的事实,而这两个类可能恰好具有相同名称的方法或实例变量。如果它们都有一个 methodA(),并且 B::methodA() 和 C::methodA() 不同,那么当某人调用 D 继承的 methodA() 时该使用哪个?更糟糕的是,如果 B 和 C 都是从一个共同的基类 A 派生的怎么办?通过不直接可实例化而避免很多这样的问题,同时仍然提供使多继承具有吸引力的接口多态性。

1
谢谢Caleb - 这让我理解了为什么无法实例化协议。 - Cortado-J

2
当您实例化一个对象时,操作系统必须知道如何在内存中分配和处理该类型的对象:它是引用类型(类)吗?强引用、弱引用还是无主引用?还是值类型(结构体、字符串、整数等)?
引用类型存储在堆中,而值类型存储在栈中。这里有一个详细的解释,介绍了两者之间的区别。
只有符合该协议的对象才能被实例化,协议本身不能被实例化,它是一种对象的一般描述或模式。
至于初始化,这里是Apple文档所说的:

初始化是准备类、结构体或枚举实例以供使用的过程。此过程涉及为该实例上的每个存储属性设置初始值,并执行任何其他设置或初始化,以便新实例准备好供使用。


我知道我做不到,但我想了解为什么编译器做不到? - Cortado-J
@Adahus 因为它足够聪明 :) - Taier

1

不幸的是,即使使用这种“hack”,Swift 也不允许这样做。

你需要使用一个符合该协议的类作为你所引用的对象。


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