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

时间:2016-12-30 17:04:43

标签: scala oop functional-programming 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")
}

不可变类

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")
    }
}  

}

2 个答案:

答案 0 :(得分:13)

首先,好消息:您的对象几乎不可变。现在,坏消息是:他们不能工作。

只有&#34;几乎&#34;不可变因为你的类不是final:我可以扩展它并覆盖方法来改变一些状态。

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

还有其他问题:您的deposit方法的返回类型为BankAccount,但它并不总是返回BankAccount:如果amount小于等于零,返回UnitBankAccountUnit的最具体的常见超类型为Any,因此您的方法实际返回Any。有多种方法可以解决这个问题,例如:返回Option[BankAccount]Try[BankAccount]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)       
}

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

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

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

最后,我想向您展示一些与现实世界银行系统实际工作方式有点接近的东西,我通过这两种方式来表示&#34;真实的银行系统世界,就像在发明电子银行之前,以及电子银行系统,因为它们实际上被使用&#34;,因为令人惊讶(或不),它们实际上是相同的。

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

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(_ + _)
}

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

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

答案 1 :(得分:8)

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

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

CREATE FUNCTION fnWeek(@StartDate DATETIME, @NumYears int)
RETURNS @Weeks TABLE (StartWeekDate DATETIME, EndWeekDate DATETIME, WeekOfYear int)
AS
BEGIN

    ;WITH genDates  
    AS (
        SELECT @StartDate AS mdate
        UNION ALL
        SELECT DATEADD(week, 1, mdate) FROM genDates WHERE DATEADD(week, 1, mdate) < DATEADD(year, @NumYears, @StartDate)
    )
    INSERT INTO @Weeks
    SELECT mdate AS StartDate, DATEADD(day,6,mdate) AS EndDate, DATEPART(week, mdate) AS WeekOfYear
    FROM genDates 
    OPTION (MAXRECURSION 0);

    RETURN;
END
GO 

select * from fnWeek('2016-01-01', 2);


+---------------------+---------------------+------+
|    StarWeektDate    |     EndWeekDate     | Week |
+---------------------+---------------------+------+
| 01.01.2016 00:00:00 | 07.01.2016 00:00:00 |   1  |
+---------------------+---------------------+------+
| 08.01.2016 00:00:00 | 14.01.2016 00:00:00 |   2  |
+---------------------+---------------------+------+
| 15.01.2016 00:00:00 | 21.01.2016 00:00:00 |   3  |
+---------------------+---------------------+------+
| 22.01.2016 00:00:00 | 28.01.2016 00:00:00 |   4  |
+---------------------+---------------------+------+
| 29.01.2016 00:00:00 | 04.02.2016 00:00:00 |   5  |
+---------------------+---------------------+------+
| 05.02.2016 00:00:00 | 11.02.2016 00:00:00 |   6  |
+---------------------+---------------------+------+
| 12.02.2016 00:00:00 | 18.02.2016 00:00:00 |   7  |
+---------------------+---------------------+------+
| 19.02.2016 00:00:00 | 25.02.2016 00:00:00 |   8  |
+---------------------+---------------------+------+
| 26.02.2016 00:00:00 | 03.03.2016 00:00:00 |   9  |
+---------------------+---------------------+------+
| 04.03.2016 00:00:00 | 10.03.2016 00:00:00 |  10  |
+---------------------+---------------------+------+
| 11.03.2016 00:00:00 | 17.03.2016 00:00:00 |  11  |
+---------------------+---------------------+------+
| 18.03.2016 00:00:00 | 24.03.2016 00:00:00 |  12  |
+---------------------+---------------------+------+
| 25.03.2016 00:00:00 | 31.03.2016 00:00:00 |  13  |
+---------------------+---------------------+------+
| 01.04.2016 00:00:00 | 07.04.2016 00:00:00 |  14  |
+---------------------+---------------------+------+
| 08.04.2016 00:00:00 | 14.04.2016 00:00:00 |  15  |
+---------------------+---------------------+------+
| 15.04.2016 00:00:00 | 21.04.2016 00:00:00 |  16  |
+---------------------+---------------------+------+
| 22.04.2016 00:00:00 | 28.04.2016 00:00:00 |  17  |
+---------------------+---------------------+------+
| 29.04.2016 00:00:00 | 05.05.2016 00:00:00 |  18  |
+---------------------+---------------------+------+
| 06.05.2016 00:00:00 | 12.05.2016 00:00:00 |  19  |
+---------------------+---------------------+------+
| 13.05.2016 00:00:00 | 19.05.2016 00:00:00 |  20  |
+---------------------+---------------------+------+
| 20.05.2016 00:00:00 | 26.05.2016 00:00:00 |  21  |
+---------------------+---------------------+------+
| 27.05.2016 00:00:00 | 02.06.2016 00:00:00 |  22  |
+---------------------+---------------------+------+
| 03.06.2016 00:00:00 | 09.06.2016 00:00:00 |  23  |
+---------------------+---------------------+------+
| 10.06.2016 00:00:00 | 16.06.2016 00:00:00 |  24  |
+---------------------+---------------------+------+
| 17.06.2016 00:00:00 | 23.06.2016 00:00:00 |  25  |
+---------------------+---------------------+------+
| 24.06.2016 00:00:00 | 30.06.2016 00:00:00 |  26  |
+---------------------+---------------------+------+
| 01.07.2016 00:00:00 | 07.07.2016 00:00:00 |  27  |
+---------------------+---------------------+------+
| 08.07.2016 00:00:00 | 14.07.2016 00:00:00 |  28  |
+---------------------+---------------------+------+
| 15.07.2016 00:00:00 | 21.07.2016 00:00:00 |  29  |
+---------------------+---------------------+------+
| 22.07.2016 00:00:00 | 28.07.2016 00:00:00 |  30  |
+---------------------+---------------------+------+
| 29.07.2016 00:00:00 | 04.08.2016 00:00:00 |  31  |
+---------------------+---------------------+------+
| 05.08.2016 00:00:00 | 11.08.2016 00:00:00 |  32  |
+---------------------+---------------------+------+
| 12.08.2016 00:00:00 | 18.08.2016 00:00:00 |  33  |
+---------------------+---------------------+------+
| 19.08.2016 00:00:00 | 25.08.2016 00:00:00 |  34  |
+---------------------+---------------------+------+
| 26.08.2016 00:00:00 | 01.09.2016 00:00:00 |  35  |
+---------------------+---------------------+------+
| 02.09.2016 00:00:00 | 08.09.2016 00:00:00 |  36  |
+---------------------+---------------------+------+
| 09.09.2016 00:00:00 | 15.09.2016 00:00:00 |  37  |
+---------------------+---------------------+------+
| 16.09.2016 00:00:00 | 22.09.2016 00:00:00 |  38  |
+---------------------+---------------------+------+
| 23.09.2016 00:00:00 | 29.09.2016 00:00:00 |  39  |
+---------------------+---------------------+------+
| 30.09.2016 00:00:00 | 06.10.2016 00:00:00 |  40  |
+---------------------+---------------------+------+
| 07.10.2016 00:00:00 | 13.10.2016 00:00:00 |  41  |
+---------------------+---------------------+------+
| 14.10.2016 00:00:00 | 20.10.2016 00:00:00 |  42  |
+---------------------+---------------------+------+
| 21.10.2016 00:00:00 | 27.10.2016 00:00:00 |  43  |
+---------------------+---------------------+------+
| 28.10.2016 00:00:00 | 03.11.2016 00:00:00 |  44  |
+---------------------+---------------------+------+
| 04.11.2016 00:00:00 | 10.11.2016 00:00:00 |  45  |
+---------------------+---------------------+------+
| 11.11.2016 00:00:00 | 17.11.2016 00:00:00 |  46  |
+---------------------+---------------------+------+
| 18.11.2016 00:00:00 | 24.11.2016 00:00:00 |  47  |
+---------------------+---------------------+------+
| 25.11.2016 00:00:00 | 01.12.2016 00:00:00 |  48  |
+---------------------+---------------------+------+
| 02.12.2016 00:00:00 | 08.12.2016 00:00:00 |  49  |
+---------------------+---------------------+------+
| 09.12.2016 00:00:00 | 15.12.2016 00:00:00 |  50  |
+---------------------+---------------------+------+
| 16.12.2016 00:00:00 | 22.12.2016 00:00:00 |  51  |
+---------------------+---------------------+------+
| 23.12.2016 00:00:00 | 29.12.2016 00:00:00 |  52  |
+---------------------+---------------------+------+
| 30.12.2016 00:00:00 | 05.01.2017 00:00:00 |  53  |
+---------------------+---------------------+------+
| 06.01.2017 00:00:00 | 12.01.2017 00:00:00 |   1  |
+---------------------+---------------------+------+
| 13.01.2017 00:00:00 | 19.01.2017 00:00:00 |   2  |
+---------------------+---------------------+------+
| 20.01.2017 00:00:00 | 26.01.2017 00:00:00 |   3  |
+---------------------+---------------------+------+
| 27.01.2017 00:00:00 | 02.02.2017 00:00:00 |   4  |
+---------------------+---------------------+------+
| 03.02.2017 00:00:00 | 09.02.2017 00:00:00 |   5  |
+---------------------+---------------------+------+
| 10.02.2017 00:00:00 | 16.02.2017 00:00:00 |   6  |
+---------------------+---------------------+------+
| 17.02.2017 00:00:00 | 23.02.2017 00:00:00 |   7  |
+---------------------+---------------------+------+
| 24.02.2017 00:00:00 | 02.03.2017 00:00:00 |   8  |
+---------------------+---------------------+------+
| 03.03.2017 00:00:00 | 09.03.2017 00:00:00 |   9  |
+---------------------+---------------------+------+
| 10.03.2017 00:00:00 | 16.03.2017 00:00:00 |  10  |
+---------------------+---------------------+------+
| 17.03.2017 00:00:00 | 23.03.2017 00:00:00 |  11  |
+---------------------+---------------------+------+
| 24.03.2017 00:00:00 | 30.03.2017 00:00:00 |  12  |
+---------------------+---------------------+------+
| 31.03.2017 00:00:00 | 06.04.2017 00:00:00 |  13  |
+---------------------+---------------------+------+
| 07.04.2017 00:00:00 | 13.04.2017 00:00:00 |  14  |
+---------------------+---------------------+------+
| 14.04.2017 00:00:00 | 20.04.2017 00:00:00 |  15  |
+---------------------+---------------------+------+
| 21.04.2017 00:00:00 | 27.04.2017 00:00:00 |  16  |
+---------------------+---------------------+------+
| 28.04.2017 00:00:00 | 04.05.2017 00:00:00 |  17  |
+---------------------+---------------------+------+
| 05.05.2017 00:00:00 | 11.05.2017 00:00:00 |  18  |
+---------------------+---------------------+------+
| 12.05.2017 00:00:00 | 18.05.2017 00:00:00 |  19  |
+---------------------+---------------------+------+
| 19.05.2017 00:00:00 | 25.05.2017 00:00:00 |  20  |
+---------------------+---------------------+------+
| 26.05.2017 00:00:00 | 01.06.2017 00:00:00 |  21  |
+---------------------+---------------------+------+
| 02.06.2017 00:00:00 | 08.06.2017 00:00:00 |  22  |
+---------------------+---------------------+------+
| 09.06.2017 00:00:00 | 15.06.2017 00:00:00 |  23  |
+---------------------+---------------------+------+
| 16.06.2017 00:00:00 | 22.06.2017 00:00:00 |  24  |
+---------------------+---------------------+------+
| 23.06.2017 00:00:00 | 29.06.2017 00:00:00 |  25  |
+---------------------+---------------------+------+
| 30.06.2017 00:00:00 | 06.07.2017 00:00:00 |  26  |
+---------------------+---------------------+------+
| 07.07.2017 00:00:00 | 13.07.2017 00:00:00 |  27  |
+---------------------+---------------------+------+
| 14.07.2017 00:00:00 | 20.07.2017 00:00:00 |  28  |
+---------------------+---------------------+------+
| 21.07.2017 00:00:00 | 27.07.2017 00:00:00 |  29  |
+---------------------+---------------------+------+
| 28.07.2017 00:00:00 | 03.08.2017 00:00:00 |  30  |
+---------------------+---------------------+------+
| 04.08.2017 00:00:00 | 10.08.2017 00:00:00 |  31  |
+---------------------+---------------------+------+
| 11.08.2017 00:00:00 | 17.08.2017 00:00:00 |  32  |
+---------------------+---------------------+------+
| 18.08.2017 00:00:00 | 24.08.2017 00:00:00 |  33  |
+---------------------+---------------------+------+
| 25.08.2017 00:00:00 | 31.08.2017 00:00:00 |  34  |
+---------------------+---------------------+------+
| 01.09.2017 00:00:00 | 07.09.2017 00:00:00 |  35  |
+---------------------+---------------------+------+
| 08.09.2017 00:00:00 | 14.09.2017 00:00:00 |  36  |
+---------------------+---------------------+------+
| 15.09.2017 00:00:00 | 21.09.2017 00:00:00 |  37  |
+---------------------+---------------------+------+
| 22.09.2017 00:00:00 | 28.09.2017 00:00:00 |  38  |
+---------------------+---------------------+------+
| 29.09.2017 00:00:00 | 05.10.2017 00:00:00 |  39  |
+---------------------+---------------------+------+
| 06.10.2017 00:00:00 | 12.10.2017 00:00:00 |  40  |
+---------------------+---------------------+------+
| 13.10.2017 00:00:00 | 19.10.2017 00:00:00 |  41  |
+---------------------+---------------------+------+
| 20.10.2017 00:00:00 | 26.10.2017 00:00:00 |  42  |
+---------------------+---------------------+------+
| 27.10.2017 00:00:00 | 02.11.2017 00:00:00 |  43  |
+---------------------+---------------------+------+
| 03.11.2017 00:00:00 | 09.11.2017 00:00:00 |  44  |
+---------------------+---------------------+------+
| 10.11.2017 00:00:00 | 16.11.2017 00:00:00 |  45  |
+---------------------+---------------------+------+
| 17.11.2017 00:00:00 | 23.11.2017 00:00:00 |  46  |
+---------------------+---------------------+------+
| 24.11.2017 00:00:00 | 30.11.2017 00:00:00 |  47  |
+---------------------+---------------------+------+
| 01.12.2017 00:00:00 | 07.12.2017 00:00:00 |  48  |
+---------------------+---------------------+------+
| 08.12.2017 00:00:00 | 14.12.2017 00:00:00 |  49  |
+---------------------+---------------------+------+
| 15.12.2017 00:00:00 | 21.12.2017 00:00:00 |  50  |
+---------------------+---------------------+------+
| 22.12.2017 00:00:00 | 28.12.2017 00:00:00 |  51  |
+---------------------+---------------------+------+
| 29.12.2017 00:00:00 | 04.01.2018 00:00:00 |  52  |
+---------------------+---------------------+------+

上述案例类代表BankAccount

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

case class BankAccount(val money: Int)

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

def deposit(bankAccount: BankAccount, money: Int): BankAccount = {
  BankAccount(money + backAccount.money)
}

在函数式编程中,创建新状态非常常见,而不是试图改变旧状态。

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