题目
实现一个不可变的银行账户类,要求支持存款、取款和查询余额操作,并处理可能的错误情况
信息
- 类型:问答
- 难度:⭐⭐
考点
不可变数据结构, 错误处理, 面向对象设计, 模式匹配
快速回答
实现要点:
- 使用
case class定义不可变账户类 - 存款/取款操作返回新实例而非修改状态
- 通过
Either或自定义ADT处理错误(如透支) - 模式匹配处理不同操作结果
- 余额使用
BigDecimal避免浮点精度问题
核心原理
在函数式编程中,不可变数据是核心原则。每次"修改"操作都应返回包含新状态的对象,而非改变原始对象。这保证了线程安全和引用透明性。
代码实现
// 定义ADT(代数数据类型)处理错误
sealed trait AccountError
case object OverdraftError extends AccountError // 透支错误
case class BankAccount private (id: String, balance: BigDecimal) {
// 存款 - 始终成功
def deposit(amount: BigDecimal): BankAccount = {
require(amount > 0, "Deposit amount must be positive")
this.copy(balance = balance + amount)
}
// 取款 - 可能失败
def withdraw(amount: BigDecimal): Either[AccountError, BankAccount] = {
require(amount > 0, "Withdrawal amount must be positive")
if (amount > balance) Left(OverdraftError)
else Right(this.copy(balance = balance - amount))
}
def getBalance: BigDecimal = balance
}
object BankAccount {
def apply(id: String): BankAccount = new BankAccount(id, 0)
}
// 使用示例
val account = BankAccount("123")
val afterDeposit = account.deposit(100.50)
val withdrawalResult = afterDeposit.withdraw(50.25)
withdrawalResult match {
case Right(newAccount) =>
println(s"New balance: ${newAccount.getBalance}") // 输出: 50.25
case Left(OverdraftError) =>
println("Error: Insufficient funds")
}最佳实践
- 不可变性:使用
case class的copy方法创建新实例 - 错误处理:
Either比异常更符合函数式原则,Left承载错误,Right承载成功结果 - 精度处理:金融计算必须使用
BigDecimal而非Double - 防御性编程:通过
require校验输入参数有效性
常见错误
- 可变状态:错误地使用
var定义余额 - 异常滥用:用
throw new Exception处理业务错误(破坏引用透明性) - 浮点精度:使用
Double导致金额计算误差 - 模式匹配缺失:未处理
Either的Left情况
扩展知识
- ADT设计:可扩展更多错误类型(如
InvalidAmountError) - 类型类:为
AccountError定义Show类型类实现错误展示 - Refined Types:使用refined库强化类型约束(如
PositiveDecimal) - IO Monad:在ZIO/Cats Effect中可将副作用延迟到边缘