关于immutability:什么时候在Scala中使用mutable和immutable类

When to use mutable vs immutable classes in Scala

关于不可变状态的优点有很多文章,但是在scala中,有没有常见的情况下更喜欢可变类?(这是一个scala新手的问题,来自某个有着"经典"OOP设计背景、使用可变类的人。)

对于一些琐碎的东西,比如三维点类,我得到了不变性的优点。但是像电机类的东西呢,它会暴露各种控制变量和/或传感器读数?一个经验丰富的scala开发人员通常会编写一个不可变的类吗?在这种情况下,"speed"在内部是否表示为"val"而不是"var","setspeed"方法是否返回类的新实例?同样,描述电机内部状态的传感器的每一个新读数是否会导致电机的新实例被实例化?

在Java或C语言中使用类来封装可变状态的"旧方法"似乎非常适合于电机示例。所以我很想知道,一旦你获得了使用不变状态范式的经验,你甚至会设计一个类,比如Motor,使其不可变。


我将使用一个不同的、经典的OO建模示例:银行账户。

在地球上的每一个OO课程中都会用到这些,而你通常最终得到的设计是这样的:

1
2
3
4
5
6
class Account(var balance: BigDecimal) {
  def transfer(amount: BigDecimal, to: Account): Unit = {
    balance -= amount
    to.balance += amount
  }
}

借据:余额是数据,转移是一个操作。(还请注意,传输是一个涉及多个可变对象的复杂操作,但是它应该是原子的,而不是复杂的…因此需要锁定等。)

然而,这是错误的。这不是银行系统的实际设计方式。事实上,这也不是真实世界(实体)银行业的运作方式。实际的实体银行和实际的银行系统的工作方式如下:

1
2
3
4
5
class Account(implicit transactionLog: TransactionLog) {
  def balance = transactionLog.reduceLeft(_ + _)
}

class TransactionSlip(from: Account, to: Account, amount: BigDecimal)

借据:余额是一个操作,传递是数据。注意这里的一切都是不变的。余额只是事务日志的左折。

还要注意,我们甚至没有以一个纯粹的功能性、不变的设计作为明确的设计目标。我们只是想对银行系统进行正确的建模,结果意外地得到了一个纯粹的功能性、不变的设计。(其实不是巧合。现实世界中的银行业之所以如此运作,有一个原因,它与编程中的好处是一样的:易变的状态和副作用使系统变得复杂和混乱……而在银行业,这意味着资金消失。)

这里的要点是,完全相同的问题可以以非常不同的方式建模,并且根据模型,您可能会想出一些简单的方法,使其完全不可变或非常困难。


我认为最有可能的答案是:是的,不变的数据结构远比您所认识到的更有用和高效。

你提出的问题有点模棱两可,因为答案取决于你描述的电机,而不是你没有描述的软件系统。在我看来,在考虑如何使用这些类之前,建议自下而上地设计"领域"类,这是OOP始终被教授的最大错误。也许您的系统甚至需要多个数据结构,以不同的方式保存关于电机的相同信息。

The"old way" of doing OOP in Java or C# using classes to encapsulate mutable state seems to fit the motor example very well.

支持多线程系统的"新方法"(有争议)是将可变状态封装到参与者中。表示电动机当前状态的行动者是可变的。但是,如果您要对电机状态进行"快照",并将该信息传递给另一个参与者,则消息必须是不可变的。

In that [immutable] case, would 'speed' be represented internally as a 'val' instead of a 'var', and the 'setSpeed' method return a new instance of the class?

是的,但是如果使用case类,实际上不必编写该方法。假设您有一个定义为case class Motor(speed: Speed, rpm: Int, mass: Mass, color: Color)的类。使用copy方法,您可以编写类似于motor2 = motor1.copy(rpm = 3500, speed = 88.mph)的内容。