技术开发 频道

Groovy 1.6的新特性

  ExpandoMetaClass DSL

  最初ExpandoMetaClass是在Grails下开发的,后来集成到了Groovy 1.5中,凭借ExpandoMetaClass,我们可以轻松改变对象和类的运行期行为而无需每次都完整的写一遍MetaClass。每次增加或是修改现有类型的属性和方法时都要做大量重复写Type.metaClass.xxx。看看下面这个示例,它来自于Unit manipulation DSL,用于处理操作符重载:

  Number.metaClass.multiply = { Amount amount -> amount.times(delegate) }

  Number.metaClass.div = { Amount amount -> amount.inverse().times(delegate) }

  Amount.metaClass.div = { Number factor -> delegate.divide(factor) }

  Amount.metaClass.div = { Amount factor -> delegate.divide(factor) }

  Amount.metaClass.multiply = { Number factor -> delegate.times(factor) }

  Amount.metaClass.power = { Number factor -> delegate.pow(factor) }

  Amount.metaClass.negative = { -> delegate.opposite() }

  显然这里有大量重复。但借助于ExpandoMetaClass DSL,我们可以通过重新分组每个类型的操作符来精简代码:

1 Number.metaClass {
2
3   multiply { Amount amount -> amount.times(delegate) }
4
5   div { Amount amount -> amount.inverse().times(delegate) }
6
7   }
8
9   Amount.metaClass {
10
11   div << { Number factor -> delegate.divide(factor) }
12
13   div << { Amount factor -> delegate.divide(factor) }
14
15   multiply { Number factor -> delegate.times(factor) }
16
17   power { Number factor -> delegate.pow(factor) }
18
19   negative { -> delegate.opposite() }
20
21   }

  metaClass()方法的唯一参数是个闭包,里面包含了各种方法和属性定义,它并没有在每一行重复Type.metaClass。如果没有同名方法就使用methodName { /* closure */ }模式,如果有就需要附加操作符并遵循methodName << { /* closure */ }模式。通过这种机制还可以增加static方法,以前典型的写法是这样的:  

1 // add a fqn() method to Class to get the fully
2
3   // qualified name of the class (ie. simply Class#getName)
4
5   Class.metaClass.static.fqn = { delegate.name }
6
7   assert String.fqn() == "java.lang.String"

  现在可以这样写:  

1 Class.metaClass {
2
3   'static' {
4
5   fqn { delegate.name }
6
7   }
8
9   }

  注意,你必须将static关键字用引号引起来,否则该构造方法看起来就像静态初始化一样。如果只增加一个方法,以前的那种方式更简洁,但要想增加多个方法,EMC DSL更适合。

  通过ExpandoMetaClass将属性加到现有类中的常见手段是添加getter和setter方法。比如,如果想要添加一个方法来计算某个文本文件中的单词数,你可能会这样做:  

1 File.metaClass.getWordCount = {
2
3   delegate.text.split(/\w/).size()
4
5   }
6
7   new File('myFile.txt').wordCount

  在getter中有些逻辑,当然这是最好的方式了,但如果仅仅想增加几个新属性来保存简单的值该怎么做呢?借助于ExpandoMetaClass,这简直是小菜一碟。在下面的示例中,我们将lastAccessed属性加到Car类中,这样每个实例都会拥有该属性。如果调用了car的某个方法,我们就会用新的时间戳更新该属性。  

1 class Car {
2
3   void turnOn() {}
4
5   void drive() {}
6
7   void turnOff() {}
8
9   }
10
11   Car.metaClass {
12
13   lastAccessed = null
14
15   invokeMethod = { String name, args ->
16
17   def metaMethod = delegate.metaClass.getMetaMethod(name, args)
18
19   if (metaMethod) {
20
21   delegate.lastAccessed = new Date()
22
23   metaMethod.doMethodInvoke(delegate, args)
24
25   } else {
26
27   throw new MissingMethodException(name, delegate.class, args)
28
29   }
30
31   }
32
33   }
34
35   def car = new Car()
36
37   println "Last accessed: ${car.lastAccessed ?: 'Never'}"
38
39   car.turnOn()
40
41   println "Last accessed: ${car.lastAccessed ?: 'Never'}"
42
43   car.drive()
44
45   sleep 1000
46
47   println "Last accessed: ${car.lastAccessed ?: 'Never'}"
48
49   sleep 1000
50
51   car.turnOff()
52
53   println "Last accessed: ${car.lastAccessed ?: 'Never'}"

  在该示例中,我们通过闭包的delegate访问属性,即delegate.lastAccessed = new Date(),通过invokeMethod()拦截所有的方法调用,invokeMethod()会将调用代理给最初的方法,如果方法不存在就抛出异常。接下来,你会看到只要实例上的方法被调用就会更新lastAccessed。

1