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

  1. class Resource private() {
  2. println("Starting transaction...")
  3. private def cleanUp() { println("Ending transaction...")}
  4. def op1 = println("Operation 1")
  5. def op2 = println("Operation 2")
  6. def op3 = println("Operation 3")
  7. }
  8. object Resource {
  9. def use(codeBlock: Resource => Unit) {
  10. val resource = new Resource
  11. try {
  12. codeBlock(resource)
  13. }
  14. finally {
  15. resource.cleanUp()
  16. }
  17. }
  18. }

我们将Resource类的构造函数标记为private。这样,就不会在这个类或者它的伴生对象之外创建出类的实例。这样一来,就只能以确定的方式使用对象了,从而保证了其行为是按照确定的方式自动执行。cleanUp()也声明为private。打印语句是真实事务操作的占位符。调用构造函数时,事务启动;隐式调用cleanUp()时,事务终结。Resource类可用的实例方法是诸如op1()op2()等。

伴生对象里有一个方法叫use(),它接收一个函数值作为参数。use()方法创建了一个Resource的实例,在tryfinally块的保护之下,把这个实例传给了给定的函数值。在finally块里,调用了Resource私有实例方法cleanUp()。相当简单,不是吗?这就是对某些必要操作提供确定性调用的全部动作。

现在,我们看看如何使用Resource类。示例代码如下:

FunctionValuesAndClosures/UseResource.scala

  1. Resource.use { resource =>
  2. resource.op1
  3. resource.op2
  4. resource.op3
  5. resource.op1
  6. }

上面代码的输出如下:

  1. Starting transaction...
  2. Operation 1
  3. Operation 2
  4. Operation 3
  5. Operation 1
  6. Ending transaction...

这里,调用了Resource伴生对象的use()方法,给它提供一个代码块,它传一个Resource实例。等到访问resource时,事务已经开启。然后,按照预期,调用Resource实例的方法(比如op1()op2()、……)。完成之后,离开代码块之际,use()会自动调用ResourcecleanUp()方法。

上面模式的一个变体是Loan模式(参见附录A)。如果想确保非内存资源得到确定性释放,就可以使用这个模式。可以这样认为这种资源密集型的对象是借给你的,用过之后应该立即归还。

下面是个使用这个模式的例子:

FunctionValuesAndClosures/WriteToFile.scala

  1. import java.io._
  2. def writeToFile(fileName: String)(codeBlock : PrintWriter => Unit) = {
  3. val writer = new PrintWriter(new File(fileName))
  4. try { codeBlock(writer) } finally { writer.close() }
  5. }

现在就可以用writeToFile()将一些内容写入文件:

FunctionValuesAndClosures/WriteToFile.scala

  1. writeToFile("output.txt") { writer =>
  2. writer write "hello from Scala"
  3. }

运行这段代码,output.txt文件内容如下:

  1. hello from Scala

作为writeToFile()方法的使用者,我们不必操心文件的关闭。在代码块里,这个文件是借给我们用的。我们可以用得到的PrintWriter实例进行写操作,一旦从这个块返回,方法就会自动关闭文件。