首先,好消息是:你的對象是差不多不可變。現在,壞消息是:他們不工作。
這些只是「幾乎」不可變的,因爲你的類不是final
:我可以擴展它並覆蓋方法來改變某些狀態。
現在,它爲什麼不工作?最明顯的錯誤是,在您的deposit
方法中,您將返回一個新的BankAccount
,其餘額設置爲已存入的金額。所以,你在存款之前就失去了所有的錢!您需要加上的存款餘額,而不是替換與存款餘額。
還有其他的問題:你的deposit
方法具有BankAccount
返回類型,但它並不總是返回BankAccount
:如果amount
小於或等於零,則返回Unit
。 BankAccount
和Unit
最具體的常見超類型是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美元!
解決這個問題並不重要,我現在就離開它。
最後,我想告訴你一些有點接近現實世界的銀行系統實際上的工作,我的意思是「銀行系統在現實世界中,如在電子銀行的發明「以及」電子銀行系統的實際使用「,因爲令人驚訝(或不),他們的工作原理是一樣的。
在您的系統中,餘額是數據和存款和提款是操作。但在現實世界中,這恰恰是雙重的:存款和取款是數據,並且計算餘額是操作。在我們製造電腦之前,銀行出納員會爲每筆交易編寫交易單,然後在一天結束時收集這些交易單,並且所有的資金流動都會加起來。而電子銀行系統的工作方式相同,大致是這樣的:
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(_ + _)
}
所以,個人的交易記錄在日誌中,而剩餘部分由加起來有賬號爲目的的所有交易金額計算並從中減去將該賬戶作爲來源的所有交易的總和。顯然有很多我沒有向你展示的實現細節,例如事務日誌是如何工作的,並且應該有一些緩存餘量,這樣你就不需要一遍又一遍的計算了。另外,我忽略了驗證(這也需要計算餘額)。
我添加了這個例子來告訴你,同樣的問題可以通過非常不同的設計來解決,而且有些設計更適合自己的功能性方法。請注意,這個第二個系統是銀行業已經存在了數十年的方式,早在計算機出現之前就已經完成了,而且它非常適合於函數式編程。
你的問題是什麼?你已經有一個案例類。使用隱式'copy'方法創建'BankAccount'的新「修改」版本。 – Carcigenicate
基本上我想知道如果我已經成功地讓類不可變,同時保持其功能 – user3704648
你永遠不會在「存款」中加入金額,你只需更換現有的餘額。再次,使用'copy'而不是構造函數來創建新的實例。如果你不使用'copy',並且爲類添加一個新的字段,你將需要修改所有的構造函數調用。除此之外,是的,它們看起來是相同的。 – Carcigenicate