【问题标题】:Loaner Pattern in ScalaScala中的借贷模式
【发布时间】:2025-12-01 16:15:01
【问题描述】:

Scala in Depth 演示了 Loaner 模式

def readFile[T](f: File)(handler: FileInputStream => T): T = {
  val resource = new java.io.FileInputStream(f)
  try {
    handler(resource)
  } finally {
      resource.close()
  }
}

示例用法:

readFile(new java.io.File("test.txt")) { input =>
   println(input.readByte)
}

此代码看起来简单明了。什么是 Scala 中 Loaner 模式的“反模式”,以便我知道如何避免它?

【问题讨论】:

  • 请注意,它在Future 上失败:readFile(...) { in => Future { println(in.readByte) } }in.readByte 可以在close 之后执行。
  • 有趣。所以,为了避免这个问题,干脆不要在这种模式下使用Future
  • 也不要返回 in 或任何可能包含 in 的内容:readFile(...){in => b: Byte => input.readByte + b}。 (注意FileInputStream中没有readByte方法)。
  • truecommons.io中有这个模式的实现。
  • 从 Scala 2.13 开始,不需要编写自己的贷款模式实现,因为有一个 Using 实用程序。 def readFile[T](f: File)(handler: FileInputStream => T): T = Using.resource(new FileInputStream(f))(handler)

标签: scala


【解决方案1】:

确保您计算的任何内容都得到积极评估,不再依赖于资源。 Scala 使惰性计算变得相当容易。比如你用这种方式包裹scala.io.Source.fromFile,你可以试试

readFile("test.txt")(_.getLines)

不幸的是,这不起作用,因为getLines 是惰性的(返回一个迭代器)。 Scala 没有任何很好的方法来指示哪些方法是惰性的,哪些不是。因此,您只需要知道(文档往往会告诉您),并且您必须在返回之前实际完成工作:

readFile("test.txt")(_.getLines.toVector)

总的来说,这是一个非常有用的模式。只需确保在退出块之前完成对资源的所有访问(因此没有未完成的期货,没有依赖于资源的惰性 val,没有迭代器,没有返回资源本身,没有尚未完全读取的流等.; 当然,如果它们不依赖于开放资源,而只依赖于基于资源的一些完全计算的数量,那么这些东西中的任何一个都是可以的。

【讨论】:

    【解决方案2】:

    对于 Loan 模式,重要的是要知道将实际调用您的借出资源的“位”代码何时会使用它。

    如果您想从贷款模式返回未来,我建议不要在传递给贷款模式函数的函数中创建它。

    不要写

    readFile("text.file")(future { doSomething })
    

    但是做:

    future { readFile("text.file")( doSomething ) }
    

    我通常会定义两种贷款模式函数:同步和异步

    所以在你的情况下,我会:

    def asyncReadFile[T](f: File)(handler: FileInputStream => T): Future[T] = {
      future{
        readFile(f)(handler)
      }
    }
    

    这样可以避免调用已关闭的资源。并且您可以重复使用您已经测试过且希望是正确的同步函数代码。

    【讨论】: