Initially developed under the Grailsumbrella and integrated back into Groovy 1.5, ExpandoMetaClass is a very handy way for changing the runtime behavior of your objects and classes, instead of writing full-blow MetaClass classes. Each time, we want to add / change several properties or methods of an existing type, there is too much of a repetition of Type.metaClass.xxx. Take for example this extract of a Unit manipulation DSL dealing with operator overloading:
- 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() }
- Number.metaClass {
- multiply { Amount amount -> amount.times(delegate) }
- div { Amount amount -> amount.inverse().times(delegate) }
- }
- Amount.metaClass {
- div << { Number factor -> delegate.divide(factor) }
- div << { Amount factor -> delegate.divide(factor) }
- multiply { Number factor -> delegate.times(factor) }
- power { Number factor -> delegate.pow(factor) }
- negative { -> delegate.opposite() }
- }
but when there are several, you should use the append operator and follow the patten methodName <<
Static methods can also be added through this mechanism, so instead of the classical approach:
- // add a fqn() method to Class to get the fully
- // qualified name of the class (ie. simply Class#getName)
- Class.metaClass.static.fqn = { delegate.name }
- assert String.fqn() == "java.lang.String"
- Class.metaClass {
- 'static' {
- fqn { delegate.name }
- }
- }
The usual approach for adding properties to existing classes through ExpandoMetaClass is to add a getter and a setter as methods. For instance, say you want to add a method that counts the number of words in a text file, you could try this:
- File.metaClass.getWordCount = {
- delegate.text.split(/\w/).size()
- }
- new File('myFile.txt').wordCount
- class Car {
- void turnOn() {}
- void drive() {}
- void turnOff() {}
- }
- Car.metaClass {
- lastAccessed = null
- invokeMethod = { String name, args ->
- def metaMethod = delegate.metaClass.getMetaMethod(name, args)
- if (metaMethod) {
- delegate.lastAccessed = new Date()
- metaMethod.doMethodInvoke(delegate, args)
- } else {
- throw new MissingMethodException(name, delegate.class, args)
- }
- }
- }
- def car = new Car()
- println "Last accessed: ${car.lastAccessed ?: 'Never'}"
- car.turnOn()
- println "Last accessed: ${car.lastAccessed ?: 'Never'}"
- car.drive()
- sleep 1000
- println "Last accessed: ${car.lastAccessed ?: 'Never'}"
- sleep 1000
- car.turnOff()
- println "Last accessed: ${car.lastAccessed ?: 'Never'}"
Supplement
* Working with closures - Declaring closures
* Working with closures - Using closures
沒有留言:
張貼留言