何时在Scala中使用可变与不可变类

时间:2013-09-30 01:56:38

标签: scala immutability

关于不可变状态的优点已经写了很多,但是在Scala中是否存在通常情况下更喜欢可变类? (这是来自使用可变类的“经典”OOP设计背景的人的Scala新手问题。)

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

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

2 个答案:

答案 0 :(得分:11)

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

这些几乎用于地球上的每个OO课程,你通常最终得到的设计是这样的:

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

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

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

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

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

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

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

这里的要点是,完全相同的问题可以用非常不同的方式建模,并且根据模型,你可能会做一些微不足道的事情来制造纯粹的不可变或非常困难。

答案 1 :(得分:4)

我认为最简单的答案很可能是:是的,不可变数据结构比你意识到的更加实用和有效。

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

  

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

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

  

在那个[immutable]的情况下,'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)的内容。