【问题标题】:What are the benefits of an immutable struct over a mutable one?与可变结构相比,不可变结构有什么好处?
【发布时间】:2015-04-12 11:38:12
【问题描述】:

我已经知道不变性相对于可变性的好处在于能够推理代码并引入更少的错误,尤其是在多线程代码中。但是,在创建结构时,我看不到创建完全不可变的结构优于可变结构的任何好处。

让我们以一个保持分数的结构为例:

struct ScoreKeeper {
    var score: Int
}

在这个结构中,我可以更改现有结构变量的 score 值

var scoreKeeper = ScoreKeeper(score: 0)
scoreKeeper.score += 5
println(scoreKeeper.score)
// prints 5

不可变版本如下所示:

struct ScoreKeeper {
    let score: Int

    func incrementScoreBy(points: Int) -> ScoreKeeper {
        return ScoreKeeper(score: self.score + points)
    }
}

及其用法:

let scoreKeeper = ScoreKeeper(score: 0)
let newScoreKeeper = scoreKeeper.incrementScoreBy(5)
println(newScoreKeeper.score)
// prints 5

我没有看到第二种方法比第一种方法的好处,因为结构是值类型。如果我传递一个结构,它总是被复制。因此,结构是否具有可变属性对我来说似乎并不重要,因为代码的其他部分无论如何都会处理单独的副本,从而消除了可变性问题。

不过,我看到有些人使用第二个示例,它需要更多代码而没有明显的好处。有什么我没有看到的好处吗?

【问题讨论】:

  • 如果整个代码库中只有一个位置创建该类型的值,那么第二个肯定只需要更多代码吗?
  • This thread 是关于 C#,但在我看来,某些论点也适用于 Swift。

标签: swift struct functional-programming


【解决方案1】:

不同的方法将有助于对代码进行不同类型的更改。不可变结构与不可变类对象非常相似,但可变结构和可变类对象非常不同。因此,如果由于某种原因需要使用类对象来代替,使用不可变结构的代码通常可以很容易地进行调整。

另一方面,使用不可变对象通常会使用修改后的版本替换变量的代码更加脆弱,以防在相关类型中添加额外的属性。例如,如果PhoneNumber 类型包含 AreaCode、LocalExchange 和 LocalNumber 的方法以及接受这些参数的构造函数,然后为 Extension 添加“可选”第四个属性,则应该更改某些区域代码的代码通过将新的区号、LocalExchange 和 LocalNumber 传递给三参数构造函数的电话号码将擦除每个电话号码的 Extension 属性,而可以直接写入 AreaCode 的代码不会有这个问题。

【讨论】:

    【解决方案2】:

    您关于复制值类型的评论非常好。也许这在特定语言(swift)和特定编译器实现(当前版本)中没有多大意义,但一般来说,如果编译器确定数据结构是不可变的,它可以例如使用参考而不是幕后的副本来获得一些性能改进。由于显而易见的原因,这不能用可变类型完成。

    更一般地说,限制意味着信息。如果你以某种方式限制你的数据结构,你就会获得一些关于它的额外知识。额外的知识意味着额外的可能性;)也许当前的编译器没有利用它们,但这并不意味着它们不在这里:)

    【讨论】:

      【解决方案3】:

      很好的分析,特别指出结构是按值传递的,因此不会被其他进程更改。

      我能看到的唯一好处是通过明确元素的不变性来实现风格上的好处。

      【讨论】:

        【解决方案4】:

        在面向对象的风格中,将基于值的类型与基于对象的类型同等对待更像是一种风格。这更多是个人选择,我认为这两者都没有什么大的好处。

        【讨论】:

          【解决方案5】:

          一般而言,不可变对象对系统的成本低于可变对象。可变对象需要具有接受新值的基础设施,并且系统必须考虑到它们的值可以随时更改的事实。

          可变对象在并发代码中也是一个挑战,因为您必须防止值从您下方从另一个线程更改出来。

          但是,如果您不断地创建和销毁独特的不可变对象,那么创建新对象的开销很快就会变得非常昂贵。

          在基础类中,NSNumber 是一个不可变对象。系统会维护一个您之前使用过的 NSNumber 对象池,并且在幕后,如果您要求一个与您之前创建的具有相同值的数字,它会返回一个现有的数字。

          这是我可以看到使用静态结构的价值的唯一情况——它们变化不大,并且可能值的池相当小。在这种情况下,您可能希望使用“工厂方法”来设置您的类,该方法可以保留最近使用的结构并在您再次要求具有相同值的结构时重用它们。

          如上所述,这样的方案可以简化并发代码。在这种情况下,您不必防止结构的值在另一个线程中发生变化。如果你使用这样的结构,你可以知道它永远不会改变。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2023-03-30
            • 2011-02-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多