6.7 Execute Around Method模式
作为一个Java程序员,你一定很熟悉synchronized
块。进入synchronized
块时,会获得给定对象的监视器(锁)。离开此块时,这个监视器会自动释放。即便是块里的代码抛出未处理的异常,也不会影响到释放。这种确定性行为非常好。然而,Java为synchronized
提供了这种机制,却没有给代码实现这种行为提供一种很好的方式。或许,用匿名内部类可以做到这一点,但是最终的代码会吓你一大跳。
幸运的是,在Scala里,实现这些构造相当容易。我们看个例子:
假设有个类Resource
,需要自动开启事务,在用完对象之后,就要显式地结束事务。正确的启动事务可以依赖于构造函数,而实现终结部分却有些棘手。这就落入Execute Around Method模式的范畴(参见Kent Beck的Smalltalk Best Practice Patterns [Bec96])。我们想在对对象进行任意一组操作的前后执行一对操作。
在Scala里,可以用函数值实现这个模式。下面是Resource
的代码,还有它的伴生对象(参见4.5节“独立对象和伴生对象”以了解伴生对象的细节):
FunctionValuesAndClosures/Resource.scala
class Resource private() {
println("Starting transaction...")
private def cleanUp() { println("Ending transaction...")}
def op1 = println("Operation 1")
def op2 = println("Operation 2")
def op3 = println("Operation 3")
}
object Resource {
def use(codeBlock: Resource => Unit) {
val resource = new Resource
try {
codeBlock(resource)
}
finally {
resource.cleanUp()
}
}
}
我们将Resource
类的构造函数标记为private
。这样,就不会在这个类或者它的伴生对象之外创建出类的实例。这样一来,就只能以确定的方式使用对象了,从而保证了其行为是按照确定的方式自动执行。cleanUp()
也声明为private
。打印语句是真实事务操作的占位符。调用构造函数时,事务启动;隐式调用cleanUp()
时,事务终结。Resource
类可用的实例方法是诸如op1()
、op2()
等。
伴生对象里有一个方法叫use()
,它接收一个函数值作为参数。use()
方法创建了一个Resource
的实例,在try
和finally
块的保护之下,把这个实例传给了给定的函数值。在finally
块里,调用了Resource
私有实例方法cleanUp()
。相当简单,不是吗?这就是对某些必要操作提供确定性调用的全部动作。
现在,我们看看如何使用Resource
类。示例代码如下:
FunctionValuesAndClosures/UseResource.scala
Resource.use { resource =>
resource.op1
resource.op2
resource.op3
resource.op1
}
上面代码的输出如下:
Starting transaction...
Operation 1
Operation 2
Operation 3
Operation 1
Ending transaction...
这里,调用了Resource
伴生对象的use()
方法,给它提供一个代码块,它传一个Resource
实例。等到访问resource
时,事务已经开启。然后,按照预期,调用Resource
实例的方法(比如op1()
、op2()
、……)。完成之后,离开代码块之际,use()
会自动调用Resource
的cleanUp()
方法。
上面模式的一个变体是Loan模式(参见附录A)。如果想确保非内存资源得到确定性释放,就可以使用这个模式。可以这样认为这种资源密集型的对象是借给你的,用过之后应该立即归还。
下面是个使用这个模式的例子:
FunctionValuesAndClosures/WriteToFile.scala
import java.io._
def writeToFile(fileName: String)(codeBlock : PrintWriter => Unit) = {
val writer = new PrintWriter(new File(fileName))
try { codeBlock(writer) } finally { writer.close() }
}
现在就可以用writeToFile()
将一些内容写入文件:
FunctionValuesAndClosures/WriteToFile.scala
writeToFile("output.txt") { writer =>
writer write "hello from Scala"
}
运行这段代码,output.txt文件内容如下:
hello from Scala
作为writeToFile()
方法的使用者,我们不必操心文件的关闭。在代码块里,这个文件是借给我们用的。我们可以用得到的PrintWriter
实例进行写操作,一旦从这个块返回,方法就会自动关闭文件。