Scala中的Loan Pattern
资源回收是计算机工程实践中一项重要的实现模式。对于具有GC的程序设计语言,它仅仅实现了内存资源的自动回收,而对于诸如文件IO,数据库连接,Socket连接等资源需要程序员自行实现资源的回收。
该问题可以形式化地描述为:给定一个资源R,并将资源传递给用户空间,并回调算法f: R => T;
当过程结束时资源自动释放。
- Input: Given resource: R
- Output:T
- Algorithm:Call back to user namespace: f: R => T, and make sure resource be closed on done.
因此,该实现模式也常常被称为「借贷模式」,是保证资源自动回收的重要机制。本文通过using的抽象控制,透视Scala在这个领域的设计技术,以便巩固「按名传递」技术的应用。
简单实现
/**
* 名字调用和值调用
*/
object manage {
def apply[R <: {def close() : Unit}, T](resource: => R)(f: R => T) = {
var res: Option[R] = None
try {
res = Some(resource)
f(res.get)
} catch {
case NonFatal(ex) => println(s"Non fatal exception! $ex")
} finally {
if(res != None) {
println(s"Closing resource ...")
res.get.close()
}
}
}
}
等价于
object using {
type Closeable = { def close(): Unit }
def apply[T <: Closeable, R](resource: => T)(f: T => R): R = {
var source = null.asInstanceOf[T]
try {
source = resource
f(source)
} finally {
if (source != null) source.close
}
}
}
统计一个文件的行数调用如下:
object TryCatchARM extends App{
def countLines(fileName:String): Unit = {
println("count file lines")
using(Source.fromFile(fileName)) { source =>
val size = source.getLines.size
println(s"file $fileName has $size lines")
if(size > 20) throw new RuntimeException("Big file")
}
}
args foreach(arg => countLines(arg))
}
R <: {def close() : Unit}
等价于type Closeable = { def close(): Unit }
定义了一个Closeable
的类型别名,使得T必须是具有close方法的子类型,这是Scala支持「鸭子编程」的一种重要技术。例如,File满足T类型的特征,它具有close方法。
这样,不管是什么对象,只要它拥有一个close():Unit的函数,都可以在这里使用,并且这个close最后将被自动调用。
惰性求值
resource: => T是按照by-name传递,在实参传递形参过程中,并未对实参进行立即求值,而将求值推延至resource: =>
T的调用点。
对于本例,using(Source.fromFile(source))
语句中,Source.fromFile(source)
并没有马上发生调用并传递给形参,而将求值推延至source = resource语句。