侧边栏壁纸
博主头像
colo

欲买桂花同载酒

  • 累计撰写 1823 篇文章
  • 累计收到 0 条评论

实现一个不可变的银行账户类,要求支持存款、取款和查询余额操作,并处理可能的错误情况

2025-12-12 / 0 评论 / 5 阅读

题目

实现一个不可变的银行账户类,要求支持存款、取款和查询余额操作,并处理可能的错误情况

信息

  • 类型:问答
  • 难度:⭐⭐

考点

不可变数据结构, 错误处理, 面向对象设计, 模式匹配

快速回答

实现要点:

  • 使用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 classcopy方法创建新实例
  • 错误处理Either比异常更符合函数式原则,Left承载错误,Right承载成功结果
  • 精度处理:金融计算必须使用BigDecimal而非Double
  • 防御性编程:通过require校验输入参数有效性

常见错误

  • 可变状态:错误地使用var定义余额
  • 异常滥用:用throw new Exception处理业务错误(破坏引用透明性)
  • 浮点精度:使用Double导致金额计算误差
  • 模式匹配缺失:未处理EitherLeft情况

扩展知识

  • ADT设计:可扩展更多错误类型(如InvalidAmountError
  • 类型类:为AccountError定义Show类型类实现错误展示
  • Refined Types:使用refined库强化类型约束(如PositiveDecimal
  • IO Monad:在ZIO/Cats Effect中可将副作用延迟到边缘