【问题标题】:In swift, why can't I instantiate a protocol when it has an initialiser?很快,为什么当协议有初始化程序时我不能实例化它?
【发布时间】:2018-12-03 17:17:17
【问题描述】:

我了解通常我无法实例化协议。 但是,如果我在协议中包含一个初始化程序,那么编译器肯定知道当协议稍后被结构或类使用时,它会有一个可以使用的 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 吗?
  • "但是如果我在协议中包含一个初始化程序,那么编译器肯定知道该协议何时被结构或类使用",不;可能相关:stackoverflow.com/questions/41074354/…

标签: swift protocols instantiation


【解决方案1】:

当你实例化一个对象时,操作系统必须知道如何分配和处理内存中的那种对象:它是引用类型(类)吗?强引用、弱引用还是无主引用?或者它是一个值类型(Structs、Strings、Int 等)?

引用类型存储在堆中,而值类型存储在堆栈中。 Here是对两者区别的详尽解释。

只能实例化引用和值类型(对象)。因此,只有符合该协议的对象才能被实例化,而不是协议本身。协议不是对象,它是对象的特定行为的一般描述或模式。

至于初始化,Apple docs 是这么说的:

初始化是准备一个类的实例的过程, 结构或枚举以供使用。这个过程包括设置一个 该实例上每个存储属性的初始值并执行 新的之前需要的任何其他设置或初始化 实例已准备就绪。

【讨论】:

  • 我明白我做不到——我只是想明白为什么编译器做不到?
  • @Adahus 因为它足够聪明:)
【解决方案2】:

不幸的是,即使有这样的“黑客”,swift也不允许这样做

您需要使用一个确认该协议的类作为您引用的对象。

【讨论】:

    【解决方案3】:

    想象协议是形容词。 Movable 说你可以 move 它,Red 说它有 color = "red"... 但他们没有说 是什么。你需要一个名词。一辆红色的可移动汽车。您可以实例化汽车,即使在细节不足的情况下也是如此。您不能实例化 Red。

    【讨论】:

    • 谢谢,我明白了。因此,如果这意味着我无法让 FindBySolvePuzzleGenerator 实例化拼图,还有其他方法可以实现我所描述的内容吗?我知道我将实现 FindBySolvePuzzleGenerator 并且它将生成拼图。我也知道它将如何做到这一点,这就是我试图在扩展中描述的内容,但看不到如何做到这一点。
    • 你为什么认为Puzzle首先需要成为协议? (或者实际上是您创建的大多数实体)。确保您也了解is-ahas-a 关系,并确保您相应地命名。举一个例子,我希望solutionPuzzle 类中的一个字段;可能是协议中名为SolvableHasSolution 或其他名称的字段。 Solution 不应该是协议。 question 绝对应该是 Puzzle 类的直接成员,因为如果它不提出问题,它就不是难题。把它变成一个协议是没有意义的。
    • 谢谢@Amadan,有用的问题。我的想法是,我的谜题可能包含非常不同的问题和解决方案。有些问题可能是一串文本,有些可能是图像,答案同样可能是可变的 - 所以我试图尽可能保持通用性以允许未来的变化。但是......这就是为什么你的问题很有用 - 即使我的问题和我的解决方案可能会有所不同,一个谜题(至少我在这里是这么想的)总是由一个问题和一个解决方案组成,所以可以一个结构而不是一个协议 - 我会尝试它......并思考你的其他 cmets。
    • 如果您有不同的问题,请创建一个抽象类Question,用不同类型的问题对其进行子类化。同样,不是协议问题。如果有意义的话,共享某些类型的问题类(例如DrawableSelectableFromAList)可能是很好的实现协议。 (您可能还希望在这些子类中包含answer,因为不同类型的问题有不同类型的答案。)
    • 通过将 Puzzle 从协议更改为结构,问题就消失了。我想我对将所有内容都更改为协议感到有点高兴。我会仔细检查,但我认为我所有的其他类型都需要保留为协议。再次感谢 @Amadan 的帮助。
    【解决方案4】:

    我知道我做不到 - 我只是想了解为什么 编译器做不到?

    因为 Swift 中的协议代表abstraction 机制。说到抽象,你可以把它想象成一个模板,我们不必关心它的行为细节或它的属性是什么;因此,能够从中创建对象是没有意义的。

    作为一个现实世界的例子,考虑到我刚才说的“表格”(作为一个抽象级别),我很确定你会明白我在说什么!然而,我们没有提及它的细节(例如它的材料或它有多少条腿......);在某些时候,如果我说“为我创建一个表”(实例化一个对象),你会问我关于规格的问题!这就是编译器不允许您直接从协议创建对象的原因。这就是将事物抽象化的意义所在。

    另外,检查:Why can't an object of abstract class be created? 可能会有所帮助。

    【讨论】:

      【解决方案5】:

      但是如果我在协议中包含一个初始化器,那么编译器肯定知道当协议稍后被结构或类使用时,它会有一个可以使用的 init?

      类必须采用协议,并且可能有十几个不同的类都采用您的Puzzle 协议。编译器不知道要实例化哪些类。

      协议赋予我们组合接口的能力,而无需多重继承的复杂性。在像 C++ 这样的多继承语言中,您必须处理这样一个事实,即单个类 D 可能继承自另外两个类 BC,而这两个类可能恰好有方法或实例变量同名。如果他们都有一个methodA(),而B::methodA()C::methodA()是不同的,当有人调用D的继承methodA()时,你使用哪一个?更糟糕的是,如果BC 都派生自一个公共基类A 怎么办?协议通过不能直接实例化来避免很多这种情况,同时仍然提供使多重继承具有吸引力的接口多态性。

      【讨论】:

      • 感谢 Caleb - 这让我了解了为什么无法实例化协议。
      猜你喜欢
      • 1970-01-01
      • 2011-05-26
      • 1970-01-01
      • 1970-01-01
      • 2012-06-01
      • 2016-07-21
      • 2015-01-09
      • 1970-01-01
      • 2011-01-20
      相关资源
      最近更新 更多