首页 文章

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

提问于
浏览
12

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

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

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

2 回答

  • 11

    我将使用另一个经典的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:余额是一种操作,转移是数据 . 请注意,这里的一切都是不可变的 . 余额只是交易日志的左侧折叠 .

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

    这里的要点是,完全相同的问题可以用非常不同的方式建模,并且取决于模型,你可能会用一些微不足道的东西来制造纯粹的不可变或非常困难 .

  • 4

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

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

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

    支持多线程系统的"new way"(可以说)是在actors中封装可变状态 . 代表电机当前状态的演员将是可变的 . 但是,如果您要获取电机状态的"snapshot"并将该信息传递给另一个演员,则该消息需要是不可变的 .

    在[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) 的内容 .

相关问题