如何在Scala中使类完全不可变

use*_*648 4 oop functional-programming scala immutability

我试图使以下类不可变.我知道如何做到这一点的理论,但我认为我的实施是错误的.你能帮我吗?

谢谢

可变类:

class BankAccount {
private var balance = 0
def deposit(amount: Int) {
    if (amount > 0)
    balance += amount
}
def withdraw(amount: Int): Int =
if (0 < amount && amount <= balance) {
    balance -= amount
    balance        
} else {
    error("insufficient funds")
}
Run Code Online (Sandbox Code Playgroud)

不变的阶级

case class BankAccount(b:Int) {

private def deposit(amount: Int):BankAccount {
    if (amount > 0)
    {
        return BankAccount(amount)
    }

}
private def withdraw(amount: Int): BankAccount ={
    if (0 < amount && amount <= balance) {
        return BankAccount(b-amount)       
    } else {
        error("insufficient funds")
    }
}  
Run Code Online (Sandbox Code Playgroud)

}

Jör*_*tag 14

首先,好消息:你的对象几乎是不可变的.现在,坏消息是:它们不起作用.

只是"几乎"不可变,因为你的类不是final:我可以扩展它并覆盖方法来改变一些状态.

现在,为什么不起作用?最明显的错误是,在您的deposit方法中,您返回一个新的BankAccount,其余额设置为已存入的金额.那么,你在存款之前就失去了那里的所有钱!您需要押金添加到余额中,而不是押金替换余额.

还有其他问题:您的deposit方法具有返回类型BankAccount,但它并不总是返回BankAccount:如果amount小于或等于零,则返回Unit.最具体的共同超BankAccountUnitAny,所以你的方法实际上返回Any.有多种方法可以解决这个问题,例如返回a Option[BankAccount],a Try[BankAccount]或an Either[SomeErrorType, BankAccount],或者只是抛出异常.对于我的例子,我只是完全忽略了验证.(存在类似的问题withdraw.)

像这样的东西:

final case class BankAccount(balance: Int) {
  private def deposit(amount: Int) = copy(balance = balance + amount)
  private def withdraw(amount: Int) = copy(balance = balance - amount)       
}
Run Code Online (Sandbox Code Playgroud)

注意我使用编译器生成的copy方法用于案例类,允许您创建仅更改了一个字段的实例的副本.在您的特定情况下,您只有一个字段,但这是一个很好的做法.

所以,这是有效的.或者......是吗?嗯,不,实际上,它没有!问题在于我们正在创建新的银行账户......其中有钱......我们正在凭空创造新的资金!如果我的帐户中有100美元,我可以提取其中的90美元,并且我会收到一个新的银行帐户对象,其中包含10美元.但我仍然可以访问100美元的旧银行帐户对象!所以,我有两个银行账户,总共110美元加上90我退出; 我现在有200美元!

解决这个问题并非易事,我现在就把它留下来.

最后,我想向您展示一些与现实世界银行系统实际工作方式有点接近的东西,我的意思是"现实世界中的银行系统,如电子银行发明之前",以及"实际使用的电子银行系统",因为令人惊讶(或不),它们实际上是相同的.

在您的系统中,余额是数据,存款和取款是操作.但在现实世界中,它正是双重的:存款和取款是数据,计算余额是一种操作.在我们掌上电脑之前,银行出纳员会为每笔交易编写交易单,然后在一天结束时收集这些交易单,并且所有的资金流动都会增加.电子银行系统也是如此,大致如下:

final case class TransactionSlip(source: BankAccount, destination: BankAccount, amount: BigDecimal)

final case class BankAccount {
  def balance =
    TransactionLog.filter(slip.destination == this).map(_.amount).reduce(_ + _) - 
    TransactionLog.filter(slip.source == this).map(_.amount).reduce(_ + _)
}
Run Code Online (Sandbox Code Playgroud)

因此,单个交易记录在日志中,余额的计算方法是将所有以该帐户作为目的地的交易金额相加,并从中减去以该帐户为来源的所有交易金额之和.很明显我没有向您展示很多实现细节,例如事务日志是如何工作的,并且可能应该对缓存进行一些缓存,这样您就不需要反复计算它.此外,我忽略了验证(这也需要计算余额).

我添加了这个例子来向您展示同一个问题可以通过非常不同的设计来解决,并且一些设计更适合于功能性方法.请注意,第二个系统是银行业务已经存在数十年的方式,早在计算机存在之前,它就非常适合功能编程.


pam*_*amu 8

在函数式编程中,您不会更改状态,而是创建新状态并返回它.

以下是使用函数式编程解决用例的方法.

case class BankAccount(val money: Int)
Run Code Online (Sandbox Code Playgroud)

上述案例类代表BankAccount

而不是改变状态,创建具有计算值的新状态并将其返回给用户.

def deposit(bankAccount: BankAccount, money: Int): BankAccount = {
  BankAccount(money + backAccount.money)
}
Run Code Online (Sandbox Code Playgroud)

以同样的方式,检查资金并创建新状态并将其返回给用户.

def withDraw(bankAccount: BankAccount, money: Int): BankAccount = {
  if (money >= 0 && bankAccount.money >= money) {
    BankAccount(bankAccount.money - money)
  } else error("in sufficient funds")
}
Run Code Online (Sandbox Code Playgroud)

在函数式编程中,创建新状态而不是尝试改变旧状态是很常见的.

创建新状态并返回,就是这样!!!