【问题标题】:When to use mutable vs immutable classes in Scala何时在 Scala 中使用可变类和不可变类
【发布时间】:2013-10-05 19:05:07
【问题描述】:

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

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

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

【问题讨论】:

标签: scala immutability


【解决方案1】:

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

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

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

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

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

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

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

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

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

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

【讨论】:

  • 我认为您在这里有了一个有趣答案的开始,但是该示例可以受益于对如何在功能表示中建模变化状态的更清晰的解释。账户余额如何“变化”?
  • 它没有,这就是重点。它是一个函数,每次查看时都会计算出来。
  • 我想我的意思是账户余额在现实世界的银行业务中确实发生了变化。这种变化必须在某个地方进行建模,并且从您的答案中不清楚确切的位置。当发生转移时,我的Account 实例是否会被替换为具有更新TransactionLog 的新实例?是什么让我的TransactionLog 与交易另一端的人的日志保持同步?还是所有帐户共享一个日志?日志本身是可变的吗?这些问题触及了您的答案似乎掩盖了的复杂性的核心。
  • 我不认为foldLeft可以这样使用,可以吗?你的意思是写reduceLeft
【解决方案2】:

我认为最有可能的简短回答是:是的,不可变数据结构比您想象的更有用和更高效。

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

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-05-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-11
    • 2010-11-20
    相关资源
    最近更新 更多