1. Execute Around Method(用完对象后立刻释放)
在Java中,进入synchronized块时,会获得给定对象的监视器(锁)。离开此块时,这个监视器会自动释放。即便是块里的代码抛出未处理的异常,也不会影响到释放。这种确定性行为非常好。
相比于Java,Scala里实现这些构造相当容易。看下面例子:
假如有个类Resource,需要自动开启食物,在用完对象之后,就要显式地结束事物。正确的启动事物可以依赖构造函数,而实现终结部分却有些棘手。这就落入Execute Around Method模式的范畴。我们想在对对象进行任意一组操作的前后执行一对操作。
在Scala中,可以用函数值实现这个模式。下面是Resource的代码,还有它的伴生对象:
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): Unit = {
val resource = new Resource
try {
codeBlock(resource)
}
finally {
resource.cleanUp()
}
}
def main(args: Array[String]): Unit = {
Resource.use{resource=>
resource.op1
resource.op2
resource.op3
}
}
}
Resource类的构造函数标记为private。这样,就不会在这个类或者它的伴生对象之外创建出类的实例。这样一来,就只能以确定的方式使用对象了,从而保证了其行为是按照确定的方式自行执行。cleanUp()也声明为private。打印语句是真事务操作的占位符。调用构造函数时,事物启动;隐式调用cleanUp()时,事务终结。Resource类可用的实例方法诸如op1()、op2()等。
伴生对象里有一个方法叫作use(),它接收一个函数值作为参数。use()方法创建了一个Resource的实例,在try和finally块里,调用了Resource私有实例方法cleanUp()。这就是对某些必要操作提供确定调用的全部动作。
运行结果如下:
Starting transaction...
Operation 1
Operation 2
Operation 3
Ending transaction...
上面模式的一个变体时Loan模式。如果非内部资源得到确定性释放,就可以使用这个模式。可以这样认为这种资源密集型的对象时借给你的,用过之后应该立即归还。
下面时个使用这个模式的例子:
def writeToFile(fileName: String)(codeBlock: PrintWriter => Unit) {
val writer = new PrintWriter(new File(fileName))
try {
codeBlock(writer)
} finally {
writer.close()
}
}
def main(args: Array[String]): Unit = {
writeToFile("output.txt") { writer =>
writer write "hello from scala" // 输出 hello from scala
}
}
作为writeToFile()方法使用者,我们不必操心文件的关闭。在代码块了,这个文件是借给我们用的。我们可以用得到的PrintWriter实例进行写操作,一旦从这个块返回,方法就会自动关闭文件。
2. 偏应用函数
使用函数可以说成是函数应用于实参。如果传入所有的预期的参数,就完全应用了这个函数。如果只传入几个参数,就会得到一个偏应用函数。这有一个便利,可以绑定几个实参,其他的留在后面填写。来看下下面这个例子:
def log(date: Date, message: String): Unit = {
println(date + "----" + message)
}
def main(args: Array[String]): Unit = {
val date = new Date
log(date, "message1")
log(date, "message2")
log(date, "message3")
}
上面的代码里,log()方法有两个参数:date和message。我们想多次调用这个方法,用相同的date、不同的message。通过把log()方法偏应用到date实参上,可以消除每次调用都要传递它的烦恼。
下面代码示例里,先将一个值绑定到date参数上,然后_使第二个参数未绑定,其结果就是一个偏应用函数,将它存到aa这个引用里。现在,就可以只用未绑定的实参message调用这个新的方法了:
val aa = log(new Date, _: String)
aa("message4")
aa("message5")
aa("message6")
当创建偏应用函数时,Scala内部会创建一个新类,它有一个特殊的apply()方法。调用偏应用函数,实际上是调用这个apply()方法。调用偏应用函数,实际上是调用这个apply方法。
3. 闭包
通常用于函数值或代码块的变量和值都是绑定的。可以清除的知道他们绑定到哪儿,局部变量或是参数。此外,还可以创建有未绑定变量的代码块。调用函数之前,必须绑定它们;不过,它们可以在局部范围和参数列表之外绑定变量。这就是称它们为闭包的原因。
看下面的例子:
def loopThrough(number: Int)(closure: Int => Unit): Unit = {
for (i <- 1 to number) {
closure(i)
}
}
loopThrough()方法接收一个代码块作为第二个参数,从1到第一个参数的范围内每个元素都会调用给定的代码块。下面定义一个代码块传给这个方法:
var result = 0
val addIt = { value: Int => result += value }
上面的例子里,定义了一个代码块,并将其赋值给变量addIt。在代码块内,变量value绑定到参数。不过,变量result在块或参数列表内是未定义的。实际上,这是绑定到代码块外部的变量result上。代码块伸长了它的手,绑定到一个外部的变量。下面是在调用loopThrough()方法时如何使用代码块:
loopThrough(10) {addIt}
println(result) // 55
loopThrough(1) {addIt}
println(result) // 56
result = 0
loopThrough(5) {addIt}
println(result) // 15
将闭包传给方法loopThrough(),value绑定到传给loopThrough()的参数上,而result则绑定到loopThrough()调用放上下文里的变量上。
绑定并不是获得变量当前值的一份副本;它实际上时绑定到变量本身。因此,如果将result的值重置为0,闭包也会看到这种变化。而且,如果闭包设置了result的值,那么主代码里也可以看到。
下面是另一个例子,闭包绑定到另一个变量product上:
var product = 1
loopThrough(5){product *= _}
println(product) // 120
在这种情况下,_指向loopThrough()所传入的参数,product绑定到loopThrough()的调用方里叫这个名字的变量上。