在Scala中何时使用可变类与不可变类
关于不可变状态的优点已经有很多文章了,但是在Scala中,有没有常见的情况表明选择可变类是有意义的呢?(这是一个Scala新手提出的问题,他有使用可变类进行“经典”OOP设计的背景。) 对于一些琐碎的东西,比如三维点类,我得到了不变性的优势。但是像Motor类的东西呢,它暴露了各种控制变量和/或传感器读数?一个经验丰富的Scala开发人员通常会编写这样一个不可变的类吗?在这种情况下,“speed”是否会在内部表示为“val”而不是“var”,并且“setSpeed”方法会返回该类的新实例?类似地,描述电机内部状态的传感器的每个新读数是否会导致电机的新实例被实例化在Scala中何时使用可变类与不可变类,scala,immutability,Scala,Immutability,关于不可变状态的优点已经有很多文章了,但是在Scala中,有没有常见的情况表明选择可变类是有意义的呢?(这是一个Scala新手提出的问题,他有使用可变类进行“经典”OOP设计的背景。) 对于一些琐碎的东西,比如三维点类,我得到了不变性的优势。但是像Motor类的东西呢,它暴露了各种控制变量和/或传感器读数?一个经验丰富的Scala开发人员通常会编写这样一个不可变的类吗?在这种情况下,“speed”是否会在内部表示为“val”而不是“var”,并且“setSpeed”方法会返回该类的新实例?类似地
在Java或C中执行OOP的“老方法”使用类封装可变状态似乎非常适合Motor示例。因此,我很想知道,一旦你获得了使用不可变状态范例的经验,你是否会设计一个像Motor这样的类,使之成为不可变的。我认为最简单的答案是:是的,不可变的数据结构比你想象的更有用、更高效 你提出的问题有点模棱两可,因为答案与其说取决于你描述的电机,不如说取决于你没有描述的软件系统。在我看来,OOP总是被教授的最大错误是,在考虑如何使用这些类之前,建议自下而上设计“领域”类。也许您的系统甚至需要多个数据结构,以不同的方式保存关于电机的相同信息 在Java或C中执行OOP的“老方法”使用类封装可变状态似乎非常适合motor示例 支持多线程系统的“新方法”(可以说)是将可变状态封装在其中。表示马达当前状态的参与者是可变的。但是,如果要对电机的状态进行“快照”,并将该信息传递给另一个参与者,则该消息必须是不可变的 在这种[immutable]情况下,“speed”是否会在内部表示为“val”而不是“var”,并且“setSpeed”方法会返回该类的新实例
是的,但是如果你使用一个。假设您有一个定义为
案例类Motor(速度:速度,rpm:Int,质量:质量,颜色:颜色)
。使用copy
方法,您可以编写类似于motor2=motor1.copy(rpm=3500,speed=88.mph)
的代码,我将使用一个不同的、经典的OO建模示例:银行账户
这在地球上几乎每一个OO课程中都有使用,你通常最终的设计是这样的:
class Account(var balance: BigDecimal) {
def transfer(amount: BigDecimal, to: Account): Unit = {
balance -= amount
to.balance += amount
}
}
class Account(implicit transactionLog: TransactionLog) {
def balance = transactionLog.reduceLeft(_ + _)
}
class TransactionSlip(from: Account, to: Account, amount: BigDecimal)
IOW:余额是数据,传输是操作。(还请注意,传输是一个涉及多个可变对象的复杂操作,但是应该是原子的,而不是复杂的…因此需要锁定等。)
然而,这是错误的。银行系统实际上并不是这样设计的。事实上,现实世界(实体)银行业也不是这样运作的。实际实体银行业务和实际银行系统的工作原理如下:
class Account(var balance: BigDecimal) {
def transfer(amount: BigDecimal, to: Account): Unit = {
balance -= amount
to.balance += amount
}
}
class Account(implicit transactionLog: TransactionLog) {
def balance = transactionLog.reduceLeft(_ + _)
}
class TransactionSlip(from: Account, to: Account, amount: BigDecimal)
IOW:余额是一个操作,传输是数据。注意这里的一切都是不可变的。余额只是事务日志的左折
还要注意的是,我们甚至没有将纯粹的功能性、不变的设计作为明确的设计目标。我们只是想对银行系统进行正确的建模,结果碰巧得到了一个纯功能的、不变的设计。(好吧,这其实不是巧合。现实世界的银行业之所以如此运作,是有原因的,而且它与编程有着相同的好处:多变的状态和副作用使系统变得复杂和混乱……而在银行业,这意味着钱消失了。)
这里的要点是,完全相同的问题可以用非常不同的方式建模,根据模型的不同,您可能会想出一些简单的方法,使其完全不可变或非常困难。这里有一个相关的问题:我认为您在这里开始了一个有趣的答案,但是,如果能更清楚地解释如何在函数表示中对不断变化的状态进行建模,该示例将受益匪浅。账户余额是如何“改变”的?它没有,这就是重点。这是一个函数,每次你看它的时候它都会被计算出来。我想我要说的是,在现实世界中,账户余额确实会发生变化。这种变化必须在某个地方进行建模,从你的答案中不清楚具体会在哪里。发生转账时,我的
帐户
实例是否会被更新了事务日志的新实例替换?是什么使我的TransactionLog
与交易另一端的个人日志保持同步?还是所有帐户都共享一个日志?日志本身是可变的吗?这些问题触及了你的答案似乎掩盖的复杂性的核心。我不认为foldLeft
可以这样使用,不是吗?你的意思是写reduceLeft
?