11

关于不可变状态的优点的文章很多,但是在 Scala 中是否有一些常见的情况下更喜欢可变类是有意义的?(这是一个 Scala 新手问题,来自具有使用可变类的“经典”OOP 设计背景的人。)

对于像 3 维 Point 类这样的微不足道的东西,我得到了不变性的优势。但是像 Motor 类这样公开各种控制变量和/或传感器读数的东西呢?经验丰富的 Scala 开发人员通常会编写这样一个不可变的类吗?在这种情况下,“速度”是否会在内部表示为“val”而不是“var”,并且“setSpeed”方法会返回该类的新实例?同样,来自描述电机内部状态的传感器的每个新读数都会导致实例化一个新的电机实例吗?

在 Java 或 C# 中使用类封装可变状态进行 OOP 的“旧方式”似乎非常适合 Motor 示例。所以我很想知道一旦你获得了使用不可变状态范式的经验,你甚至会设计一个像 Motor 这样的类是不可变的。

4

2 回答 2

13

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

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

class Account(var balance: BigDecimal) {
  def transfer(amount: BigDecimal, to: Account): Unit = { 
    balance -= amount
    to.balance += amount
  }
}

IOW:余额是数据,转账是操作。(另请注意,传输是涉及多个可变对象的复杂操作,但是应该是原子的,而不是复杂的......所以你需要锁定等)

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

class Account(implicit transactionLog: TransactionLog) {
  def balance = transactionLog.reduceLeft(_ + _)
}

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

IOW:余额是操作,转账是数据。请注意,这里的一切都是不可变的。余额只是交易日志的左侧折叠。

另请注意,我们甚至没有将纯粹的功能性、不可变设计作为明确的设计目标。我们只是想正确地为银行系统建模,巧合的是,我们最终得到了一个纯粹的功能性、不可变的设计。(嗯,这实际上并非巧合。现实世界的银行业务以这种方式工作是有原因的,并且它具有与编程相同的好处:可变状态和副作用使系统变得复杂和混乱……而在银行业务中,这意味着钱不见了。)

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

于 2013-09-30T11:39:32.163 回答
4

我认为最有可能的简短答案是:是的,不可变数据结构比您意识到的要实用和高效得多。

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

在 Java 或 C# 中使用类来封装可变状态的 OOP 的“旧方式”似乎非常适合电机示例。

支持多线程系统的“新方法”(可以说)是将可变状态封装在actor中。代表电机当前状态的参与者是可变的。但是,如果您要拍摄电机状态的“快照”并将该信息传递给另一个参与者,则消息需要是不可变的。

在那种 [不可变] 的情况下,“速度”会在内部表示为“val”而不是“var”,并且“setSpeed”方法会返回该类的新实例吗?

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

于 2013-09-30T03:05:34.190 回答