First they ignore you
then they ridicule you
then they fight you
then you win
    -- Mahatma Gandhi
Chinese => English     英文 => 中文             
随笔-179  评论-1027  文章-0  trackbacks-0
从Groovy1.1beta-2开始,实现动态改变对象的能力变的十分简单:

一开始,我们有这样一个类:
class Person {
    String name
}

该类的实例都是哑巴,不能说话,作为造物主的我们该完善它们,使它们能自我介绍(添加实例方法)
class Person {
    String name
}

// 添加自我介绍的行为
Person.metaClass.introduce << {println "I'm $name"}

现在让我们看看,它们到底是否真的能够开口自我介绍了呢:
class Person {
    String name
}

// 添加自我介绍的行为
Person.metaClass.introduce << {println "I'm $name"}
def person 
= new Person(name:"山风小子")
person.introduce()

运行结果:
I'm 山风小子

嗯~人类改造成功~

但人应该有性别吧,嗯~对的,加个性别属性sex(添加属性)
class Person {
    String name
}

// 添加自我介绍的行为
Person.metaClass.introduce << {println "I'm $name"}
// 添加性别属性,默认为男(Male)
Person.metaClass.sex = "Male"
def person 
= new Person(name:"山风小子")
person.introduce()
println person.sex

运行结果:
I'm 山风小子
Male


但做男人累啊~为了买房,娶妻拼命赚钱,做女人算了,做变性手术:
class Person {
    String name
}

// 添加自我介绍的行为
Person.metaClass.introduce << {println "I'm $name"}
// 添加性别属性,默认为男(Male)
Person.metaClass.sex = "Male"
def person 
= new Person(name:"山风小子")
person.introduce()
println person.sex
// 做变性手术,变为女的(Female)
person.sex = "Female"
println person.sex

运行结果:
I'm 山风小子
Male
Female

作为造物主的我们考虑到手术的风险性,为了让其他人知道自己现在是个女的,在介绍中添加性别说明:
class Person {
    String name
}

// 添加自我介绍的行为
Person.metaClass.introduce << {println "I'm $name"}
// 添加性别属性,默认为男(Male)
Person.metaClass.sex = "Male"
// 修改之前自我介绍行为,添加性别说明
Person.metaClass.introduce << {println "I'm $name, $sex"}
def person 
= new Person(name:"山风小子")
person.introduce()
// 做变性手术,变为女的(Female)
person.sex = "Female"
person.introduce()

运行结果:
I'm 山风小子, Male
I'm 山风小子, Female

为了造人方便点,搞个工厂方法(添加类方法,即静态方法)
class Person {
    String name
}
// 添加自我介绍的行为
Person.metaClass.introduce << {println "I'm $name"}
// 添加性别属性,默认为男(Male)
Person.metaClass.sex = "Male"
// 修改之前自我介绍行为,添加性别说明
Person.metaClass.introduce << {println "I'm $name, $sex"}
def person 
= new Person(name:"山风小子")
person.introduce()
// 做变性手术,变为女的(Female)
person.sex = "Female"
person.introduce()
// 工厂方法,造人方便点
Person.metaClass.'static'.createPerson = { name, sex ->
    Person p 
= new Person()
    p.name 
= name
    p.sex 
= sex
    
return p
}
def bluesun 
= Person.createPerson("山风小子""Male")
bluesun.introduce()

运行结果:
I'm 山风小子, Male
I'm 山风小子, Female
I'm 山风小子, Male

为了方便实例化Person,添加一个构造方法(添加构造方法)
class Person {
    String name
}
// 添加自我介绍的行为
Person.metaClass.introduce << {println "I'm $name"}
// 添加性别属性,默认为男(Male)
Person.metaClass.sex = "Male"
// 修改之前自我介绍行为,添加性别说明
Person.metaClass.introduce << {println "I'm $name, $sex"}
def person 
= new Person(name:"山风小子")
person.introduce()
// 做变性手术,变为女的(Female)
person.sex = "Female"
person.introduce()
// 工厂方法,造人方便点
Person.metaClass.'static'.createPerson = { name, sex ->
    Person p 
= new Person()
    p.name 
= name
    p.sex 
= sex
    
return p
}
def bluesun 
= Person.createPerson("山风小子""Male")
bluesun.introduce()
// 方便实例化Person,添加一个构造方法
Person.metaClass.constructor << { name, sex ->
    
new Person(name:name, sex:sex)
}
def daniel 
= new Person("Daniel""Male")
daniel.introduce()

运行结果:
I'm 山风小子, Male
I'm 山风小子, Female
I'm 山风小子, Male
I'm Daniel, Male

最后,引用一个官方例子swapCase来展示一下Groovy是如何增强既有类的能力的(演示如何使用delegate,注意演示的是final类:String)
String.metaClass.swapCase = {->
      def sb 
= new StringBuffer()
      
// delegate与this类似,引用当前正被‘改造’的对象
      delegate.each {
           sb 
<< (Character.isUpperCase(it as char? Character.toLowerCase(it as char) : 
                   Character.toUpperCase(it as 
char))
      }
      sb.toString()
}

String s 
= "Hello, world!"
println s.swapCase()

运行结果:
hELLO, WORLD!

<<用于添加方法(如果方法已经存在,会发生groovy.lang.GroovyRuntimeException异常),=用于添加方法或覆盖既有方法



而从Groovy1.1beta-3开始,Groovy的动态性有了进一步的增强:

我们可以通过respondsTo和hasProperty方法来判断是否存在某个方法和某个属性:
class Person {
    String name

    
public Person(name) {
        
this.name = name
    }

    def introduce() {
        println 
"I'm $name"
    }

    def introduce(String name) {
        println 
"She is $name"
    }
}

def daniel 
= new Person('Daniel')

// 判断实例daniel是否有方法introduce()
if (daniel.metaClass.respondsTo(daniel, 'introduce')) {
    daniel.introduce()
}

// 判断实例daniel是否有方法introduce(String)
if (daniel.metaClass.respondsTo(daniel, 'introduce', String)) {
    daniel.introduce(
'Annie')
}

// 判断实例daniel是否有属性name
if (daniel.metaClass.hasProperty(daniel, 'name')) {
    println daniel.name
}

运行结果:
I'm Daniel
She is Annie
Daniel


使用methodMissing方法来处理那些不存在的方法的调用
class Person {
    String name

    
public Person(name) {
        
this.name = name
    }

    def introduce() {
        println 
"I'm $name"
    }

    def introduce(String name) {
        println 
"She is $name"
    }
}

Person.metaClass.methodMissing 
= { name, args ->
    
// 动态添加方法
    Person.metaClass."$name" = { methodArgs ->
        
if ("hello".equals(name))
            println 
"$methodArgs"
        
else {
            def argList 
= Arrays.asList(methodArgs)
            println 
"No method $name with ${argList*.class}"
        }
    }

   delegate.
"$name"(args)
}

def daniel 
= new Person('Daniel')

daniel.hello(
"Leona")
daniel.hi(
"Annie""Leona")

运行结果:
{"Leona"}
No method hi with [class java.lang.String, class java.lang.String]


类似地,使用propertyMissing方法来处理那些不存在的属性的引用
class Person {
    String name

    
public Person(name) {
        
this.name = name
    }

    def introduce() {
        println 
"I'm $name"
    }

    def introduce(String name) {
        println 
"She is $name"
    }
}

Person.metaClass.propertyMissing 
= { String name, value ->
    
// 动态添加属性
    Person.metaClass."$name" = value
}

def daniel 
= new Person('Daniel')
daniel.sex 
= 'Male'
println daniel.sex

运行结果:
Male


而从Groovy1.6beta-2-snapshot开始,Groovy在这方面的语法更加简洁了(当然原来prototype式的语法也同样支持):

String.metaClass {
    
'static' {
        value 
= {
            it.toString()
        }
    }
    
    swapCase 
= { ->
      def sb 
= new StringBuffer()
      
// delegate与this类似,引用当前正被‘改造’的对象
      delegate.each {
           sb 
<< (Character.isUpperCase(it as char? Character.toLowerCase(it as char) : 
                   Character.toUpperCase(it as 
char))
      }
      sb.toString()
    }
}

String s 
= 'Hello, world!'
println s.swapCase()
println String.value(
new Date())

运行结果:
hELLO, WORLD!
Sun Jun 08 14:53:28 CST 2008



附:朝花夕拾——Groovy & Grails
posted on 2007-07-15 00:50 山风小子 阅读(1978) 评论(12)  编辑  收藏 所属分类: Groovy & Grails

评论:
# re: Groovy高效编程——动态改变对象的能力[未登录] 2007-07-15 08:23 | 黄鹏霄
顶,象JAVASCRIPT的prototype  回复  更多评论
  
# re: Groovy高效编程——动态改变对象的能力 2007-07-15 08:45 | 山风小子
@黄鹏霄
嗯,这个特性使得Groovy的动态性增强了不少 :)  回复  更多评论
  
# re: Groovy高效编程——动态改变对象的能力[未登录] 2007-07-15 09:14 | bluesky
不抽象了啊,直接搞啊  回复  更多评论
  
# re: Groovy高效编程——动态改变对象的能力 2007-07-15 09:17 | 山风小子
@bluesky
See 《Effective Java》 :)  回复  更多评论
  
# re: Groovy高效编程——动态改变对象的能力 2007-07-15 15:10 | ehe
不错.  回复  更多评论
  
# re: Groovy高效编程——动态改变对象的能力 2007-07-15 15:29 | 山风小子
@ehe
谢谢 :)  回复  更多评论
  
# re: Groovy高效编程——动态改变对象的能力 2007-07-15 16:25 | dennis
非常有趣,不过还是不够爽,groovy如果能直接支持open class就更好了,现在的风格更类似javascript  回复  更多评论
  
# re: Groovy高效编程——动态改变对象的能力 2007-07-15 17:00 | 山风小子
open class?
这个似乎是ruby世界中的名词,能举个例子吗?
谢谢 :)  回复  更多评论
  
# re: Groovy高效编程——动态改变对象的能力 2007-07-15 18:40 | dennis
@山风小子
太客气了,open class只是看起来更直观一点,比如
class Person
attr_accessor:name
end
Person类有name名称,我在某个地方用到这个Person类,并且想给他添加个属性mail,我不需要去修改Person源码,打开Person类,直接写就是了:
class Person
attr_accessor:mail
end
此时的Person类就有两个属性,注意仅限在你的源码中,其他人可以在别的地方添加属性。
我们也可以随时随地地为内建的类添加方法或者属性,比如,我们打开String类,添加个introduce方法很简单:
class String
def introduce
"It's me!"
end
end
调用下:“hello".introduce,输出:
"It's me!"
显然,"hello"是String类。

  回复  更多评论
  
# re: Groovy高效编程——动态改变对象的能力 2007-07-15 20:28 | 海边沫沫
open class,爽是爽了,不过有点恐怖。

还是prototype或者这里的metaClass比较好一点。  回复  更多评论
  
# re: Groovy高效编程——动态改变对象的能力 2007-07-15 22:53 | 山风小子
@dennis
谢谢您举的这个例子,作为一个ruby外行来看,
原本的类定义是
class Person
attr_accessor:name
end
现在添加mail属性
class Person
attr_accessor:mail
end
如果不知内情,看代码的朋友还以为我在定义只有mail属性的Person呢,但其实我在添加属性,所以个人觉得open class这种方式的可读性不是很好。  回复  更多评论
  
# re: Groovy高效编程——动态改变对象的能力 2007-07-15 22:54 | 山风小子
@海边沫沫
英雄所见略同  回复  更多评论
  


标题  
姓名  
主页
验证码 *  
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2008-06-08 14:54 编辑过