【问题标题】:Convenience vs multiple inits Swift方便 vs 多个初始化 Swift
【发布时间】:2020-06-22 23:44:37
【问题描述】:

我在这里阅读了对类似问题的回复,但我仍然对这里最好的实现方式、原因以及有什么不同感到困惑。

在这个实现中,我有两个类的初始化器

class Person1 {
  var name: String
  var age: Int
  var nationality: String

  init(name: String, age: Int,  nationality: String) {
    self.name = name
    self.age = age
    self.nationality = nationality
  }

  init(name:String, age: Int) {
    self.name = name
    self.age = age
    self.nationality = "Canadian"
  }
}

当我创建一个实例时,我可以选择其中一个。如果我选择第二个,我将默认国籍设置为加拿大

  let p1 = Person1(name: "Stewart", age: 40)

如果我将第二个初始化器替换为以下便利初始化器,

convenience init(name: String, age: Int) {
    self.init(name: name, age: age, nationality: "Canadian")
}

我可以用同样的方式创建同一个实例并得到同样的结果。那么区别是什么呢?我发现很难找到一个好的示例,其中便利初始化程序是完成创建类的特定实例的唯一方法,而第二个初始化程序无法像我在第一个示例中所做的那样完成。

【问题讨论】:

  • 只有在子类化时才真正有趣。
  • 为什么不简单地为您的国籍添加一个默认值? init(name: String, age: Int, nationality: String = "Canadian") { 无需创建 2 个初始化器
  • 我可以这样做,但它不能回答我的问题。我正在尝试确定一种方式是否优于另一种方式。
  • @matt,所以你的意思是在我的实现中,一种方式与另一种方式一样好,因为除非我将类子类化,否则它没有区别?
  • 这并没有足够引人注目的差异来引起人们的兴趣。您是在说“它们之间没有什么可以选择的”。你是对的,因为在你使用的例子中,这是一个品味问题。但是,如果我们是子类化或被子类化,初始化器是指定初始化器还是便利初始化器会产生巨大的差异。

标签: swift class initializer convenience-methods


【解决方案1】:

事实上,拥有两个初始化程序是 Bad Code™。正如 Leo Dabus 所说,使用最合适的 Swift 语言功能来表达该确切示例的方法是使用默认参数值。

class Person1 {
  var name: String
  var age: Int
  var nationality: String

  init(
    name: String,
    age: Int,
    nationality: String = "Canadian"
  ) {
    self.name = name
    self.age = age
    self.nationality = nationality
  }
}

当存在另一个初始化器时,您可以使用便捷初始化器,它可以节省您的代码重复。

例如,假设您添加了一个超类,并带有所需的 init:

class Person0 {
  var name: String
  var age: Int

  required init(
    name: String,
    age: Int
  ) {
    self.name = name
    self.age = age
  }
}

你不能只这样做; Swift 认为这不能满足要求:

class Person1: Person0 {
  var nationality: String

  required init(
    name: String,
    age: Int,
    nationality: String = "Canadian"
  ) {
    self.nationality = nationality
    super.init(
      name: name,
      age: age
    )
  }
}

相反,您可以复制,如下所示:

  required init(
    name: String,
    age: Int
  ) {
    nationality = "Canadian"
    super.init(
      name: name,
      age: age
    )
  }

  init(
    name: String,
    age: Int,
    nationality: String
  ) {
    self.nationality = nationality
    super.init(
      name: name,
      age: age
    )
  }

…或者使用一个指定的初始化器,它实际完成工作,并将所需的初始化器转发给它:

  required convenience init(
    name: String,
    age: Int
  ) {
    self.init(
      name: name,
      age: age,
      nationality: "Canadian"
    )
  }

  init(
    name: String,
    age: Int,
    nationality: String
  ) {
    self.nationality = nationality
    super.init(
      name: name,
      age: age
    )
  }

我发现很难找到一个好的示例,其中便利初始化程序是完成创建类的特定实例的唯一方法,而第二个初始化程序无法像我在第一个示例中所做的那样完成。

您始终可以复制代码。便利初始化器永远不会是唯一的方法。在最简单的情况下,就像我在这里展示的那样,每个初始化程序只有部分重复行。这没什么大不了的。但是重复数据删除会随着继承而堆积起来。

如果您的子类提供其所有超类指定初始化器的实现(通过按照规则 1 继承它们,或者通过提供自定义实现作为其定义的一部分),那么它会自动继承所有超类便利初始化器。 – https://docs.swift.org/swift-book/LanguageGuide/Initialization.html

因此,例如,当您使用便利 + 指定组合时,您可以跳过重写所需的 init。

例如

Person2(name: "Terrance Phillip", age: 42)

从这里编译:

class Person2: Person1 {
  var flag: String

  override convenience init(
    name: String,
    age: Int,
    nationality: String
  ) {
    self.init(
      name: name,
      age: age,
      nationality: nationality,
      flag: "??"
    )
  }

  init(
    name: String,
    age: Int,
    nationality: String,
    flag: String
  ) {
    self.flag = flag
    super.init(
      name: name,
      age: age,
      nationality: nationality
    )
  }
}

这也是这个人:

Person3()

...鉴于此:

class Person3: Person2 {
  convenience init() {
    self.init(
      name: "Terrance Phillip",
      age: 42
    )
  }
}

作为指导,尽可能使用 1. 默认参数和 2. 便利初始化器。 (并且作为 2 的扩展,在子类中将指定的初始化器转换为方便的初始化器。)您将需要维护更少的代码。

【讨论】:

  • 非常感谢这里的详细回复。喜欢你在 RW 上的东西
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-11
  • 1970-01-01
  • 2011-09-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多