配色: 字号:
ruby的元编程
2012-04-03 | 阅:  转:  |  分享 
  
元编程(MetaProgram),就是指用一段程序生成另一段程序。广义的讲,类似Java里基于XML的代码生成和PHP里基于__call的动态代理等都可以算作元编程,不过今天我要介绍的是Ruby里的元编程。Ruby里的元编程例子很多,attr_accessor算是最容易想到的例子了,通过设定attr_accessor,Ruby解析器会自动给我们的代码加上setter/getter功能,其实实现起来还是相当简单的,下面我们自己实现一个类似attr_accessor的方法(var):建立一个var.rb文件,内容如下:在irb环境下进行如下测试:irb(main):001:0>load"/path/to/var.rb"=>trueirb(main):002:0>classPersonirb(main):003:1>var:name,:ageirb(main):004:1>end=>[:name,:age]irb(main):005:0>p=Person.new=>#irb(main):006:0>p.name="laowang"=>"laowang"irb(main):007:0>p.name=>"laowang"OK了,我们创建了一个和attr_accessor功能一样的var方法,这就是Ruby中的元编程。

(2)元编程的探索之路

http://blog.csdn.net/rocky_j2ee/article/details/3755054§



我最近考虑了很多元编程(Metaprogramming)的问题,并希望看到更多这方面技术的例子和讲解。无论好坏,元编程已经进入Ruby社区,并成为完成各种任务和简化代码的标准方式。既然找不到这类资源,我准备抛砖引玉写一些通用Ruby技术的文章。这些内容可能对从其它语言转向Ruby或者还没有体验到Ruby元编程乐趣的程序员非常有用。1.使用单例类Usethesingleton-class许多操作单个对象的方法是基于操作其单例类(singletonclass),并且这样可以使元编程更简单。获得单例类的经典方法是执行如下代码:

Ruby代码§

sclass=(class<
[ruby]viewplain§copy§



sclass=(class<
RCR231建议这样定义Kernel#singleton_class方法:

Ruby代码§

moduleKernel

defsingleton_class

class<
end

end

[ruby]viewplain§copy§



moduleKernel

defsingleton_class

class<
end

end

我会在下文使用这个方法。2.DSL的使用类方法来修改子类WriteDSL''susingclass-methodsthatrewritesubclasses当你想创建一个DSL来定义类信息时,最常见的问题是怎样表示信息来让框架的其它部分使用。以定义一个ActiveRecord模型对象为例:

Ruby代码§

classProduct
set_table_name''produce''

end

[ruby]viewplain§copy§



classProduct
set_table_name''produce''

end

在这个例子中,令人感兴趣的是set_table_name的使用。这是怎么起作用的呢?好吧,这里涉及到一个小魔法。这是一种实现方法:

Ruby代码§

moduleActiveRecord

classBase

defself.set_table_namename

define_attr_method:table_name,name

end

defself.define_attr_method(name,value)

singleton_class.send:alias_method,"original_#{name}",name

singleton_class.class_evaldo

define_method(name)do

value

end

end

end

end

end

[ruby]viewplain§copy§



moduleActiveRecord

classBase

defself.set_table_namename

define_attr_method:table_name,name

end

defself.define_attr_method(name,value)

singleton_class.send:alias_method,"original_#{name}",name

singleton_class.class_evaldo

define_method(name)do

value

end

end

end

end

end

这里令人感兴趣的是define_attr_method。在这个例子中我们需要获得Product类的单例类,但又不想修改ActiveRecord::Base。通过使用单例类我们达到了这个目的。我们为原来的方法取别名,再定义新的存取器(accessor)来返回值。如果ActiveRecord需要tablename就可以直接调用存取器。这种动态创建方法和存取器的技术在单例类是很常见的,特别是Rails。3.动态创建class和moduleCreateclassesandmodulesdynamicallyRuby允许你动态创建和修改class和module。你可以在没有冻结的class或module上做任何修改。特定情况下会很有用。Struct类可能是最好的例子:

Ruby代码§

PersonVO=Struct.new(:name,:phone,:email)

p1=PersonVO.new(:name=>"OlaBini")

[ruby]viewplain§copy§



PersonVO=Struct.new(:name,:phone,:email)

p1=PersonVO.new(:name=>"OlaBini")

这会创建一个新类,并赋给PersonVO,然后创建一个类的实例。从草稿创建新类并定义新方法也很简单:

Ruby代码§

c=Class.new

c.class_evaldo

define_method:foodo

puts"HelloWorld"

end

end

c.new.foo#=>"HelloWorld"

[ruby]viewplain§copy§



c=Class.new

c.class_evaldo

define_method:foodo

puts"HelloWorld"

end

end

c.new.foo#=>"HelloWorld"

除了Struct,还能在SOAP4R和Camping找到轻松创建类的例子。Camping尤其令人感兴趣,因为它有专门的方法创建这些类,被你的controller和view继承。Camping的许多有趣的功能都是用这种方式实现的:

Ruby代码§

defR(urls);Class.new(R){meta_def(:urls){urls}};

end

[ruby]viewplain§copy§



defR(urls);Class.new(R){meta_def(:urls){urls}};

end

这使得可以这样创建controller:

Ruby代码§

classView
defgetpost_id

end

end

[ruby]viewplain§copy§



classView
defgetpost_id

end

end

你也可以这样创建module,然后在类中包含module。4.使用method_missing来做有趣的事Usemethod_missingtodointerestingthings除了闭包(block),method_missing可能是Ruby最强大的特性,也是最容易滥用的一个。用好method_missing的话有些代码会变得超级简单,甚至是不能缺少。一个好的例子(Camping)是扩展Hash:

Ruby代码§

classHash

defmethod_missing(m,a)

ifm.to_s=~/=$/

self[$`]=a[0]

elsifa.empty?

self[m]

else

raiseNoMethodError,"#{m}"

end

end

end

[ruby]viewplain§copy§



classHash

defmethod_missing(m,a)

ifm.to_s=~/=$/

self[$`]=a[0]

elsifa.empty?

self[m]

else

raiseNoMethodError,"#{m}"

end

end

end

就可以这样使用hash:

Ruby代码§

x={''abc''=>123}

x.abc#=>123

x.foo=:baz

x#=>{''abc''=>123,''foo''=>:baz}

[ruby]viewplain§copy§



x={''abc''=>123}

x.abc#=>123

x.foo=:baz

x#=>{''abc''=>123,''foo''=>:baz}

如你所见,如果有人调用了一个hash不存在的方法,则会搜索内部集合。如果方法名以=结尾,则会赋给同名的key。Markaby中可以找到另一个很好的method_missing技巧。以下引用的代码可以生成任何包含CSSclass的XHTML标签:

Ruby代码§

bodydo

h1.header''Blog''

div.contentdo

''Hellu''

end

end

[ruby]viewplain§copy§



bodydo

h1.header''Blog''

div.contentdo

''Hellu''

end

end

会生成:

Xml代码§



Blog



Hellu





[xml]viewplain§copy§





Blog



Hellu





绝大多数这种功能,特别是CSSclass名是通过method_missing设置了self的属性然后返回self。5.方法模式的调度Dispatchonmethod-patterns这对于无法预测的方法来说可以轻松的达到可扩展性。我最近创建了一个小型验证框架,核心的验证类会找出自身所有以check_开头的方法并调用,这样就可以轻松地增加新的验证:只要往类或实例中添加新方法。

Ruby代码§

methods.grep/^check_/do|m|

self.sendm

end

[ruby]viewplain§copy§



methods.grep/^check_/do|m|

self.sendm

end

这非常简单,并且难以置信的强大。可以看一下Test::Unit到处使用这种方法。6.替换方法Replacingmethods有时候一个方法的实现不是你要的,或者只做了一半。标准的面向对象方法是继承并重载,再调用父类方法。仅当你有对象实例化的控制权时才有用,经常不是这种情况,继承也就没有价值。为得到同样的功能,可以重命名(alias)旧方法,并添加一个新的方法定义来调用旧方法,并确保旧方法的前后条件得到保留。

Ruby代码§

classString

alias_method:original_reverse,:reverse

defreverse

puts"reversing,pleasewait..."original_reverse

end

end

[ruby]viewplain§copy§



classString

alias_method:original_reverse,:reverse

defreverse

puts"reversing,pleasewait..."original_reverse

end

end

一个极端的用法是临时修改一个方法,然后再还原。例如:

Ruby代码§

deftrace(mths)

add_tracing(mths)#aliasesthemethodsnamed,addingtracing

yield

remove_tracing(mths)#removesthetracingaliases

end

[ruby]viewplain§copy§



deftrace(mths)

add_tracing(mths)#aliasesthemethodsnamed,addingtracing

yield

remove_tracing(mths)#removesthetracingaliases

end

这个例子展示了编写add_tracing和remove_tracing的一种典型方法。它依赖于第1条的单例类:

Ruby代码§

classObject

defadd_tracing(mths)

mths.eachdo|m|

singleton_class.send:alias_method,"traced_#{m}",m

singleton_class.send:define_method,mdo|args|

$stderr.puts"before#{m}(#{args.inspect})"

ret=self.send("traced_#{m}",args)

$stderr.puts"after#{m}-#{ret.inspect}"

ret

end

end

end

defremove_tracing(mths)

mths.eachdo|m|

singleton_class.send:alias_method,m,"traced_#{m}"

end

end

end

"abc".add_tracing:reverse

[ruby]viewplain§copy§



classObject

defadd_tracing(mths)

mths.eachdo|m|

singleton_class.send:alias_method,"traced_#{m}",m

singleton_class.send:define_method,mdo|args|

$stderr.puts"before#{m}(#{args.inspect})"

ret=self.send("traced_#{m}",args)

$stderr.puts"after#{m}-#{ret.inspect}"

ret

end

end

end

defremove_tracing(mths)

mths.eachdo|m|

singleton_class.send:alias_method,m,"traced_#{m}"

end

end

end

"abc".add_tracing:reverse

如果这些方法是添加到module(有一点点不同,看你能不能写出来!),你也可以在类而非实例上添加和删除tracing。7.使用nil类来引入空对象的重构UseNilClasstoimplementtheIntroduceNullObjectrefactoring在Fowler的重构中,“引入空对象”的重构是一个对象要么存在,要么为空时有一个预定义值。典型例子如下:

Ruby代码§

name=x.nil??"defaultname":x.name

[ruby]viewplain§copy§



name=x.nil??"defaultname":x.name

目前基于Java的重构会推荐创建一个类似于null的子类。例如NullPerson会继承Person,重载name方法总是返回"defaultname"。但是在Ruby中我们可以打开类,可以这样做:

Ruby代码§

defnil.name;"defaultname";end

x#=>nil

name=x.name#=>"defaultname"

[ruby]viewplain§copy§



defnil.name;"defaultname";end

x#=>nil

name=x.name#=>"defaultname"

8.学习eval的不同版本LearnthedifferentversionsofevalRuby有几种版本的执行方法(evaluation)。了解它们的区别和使用情景是很重要的。有eval、instance_eval、module_eval和class_eval几种。首先,class_eval是module_eval的别名。其次,eval和其他的有些不同。最重要的是eval只能够执行一个字符串,其它的可以执行block。这意味着eval是你做任何事的最后选择,它有它的用处,但绝大多数情况下应该用instance_eval和module_eval执行block。eval会在当前环境执行字符串,除非环境已经提供绑定(binding)。(见第11条)instance_eval会在接收者(reveiver)的上下文中执行字符串或block,没有指定的话self会作为接收者。module_eval会在调用的module的上下文中执行字符串或block。这个比较适合在module或单例类中定义新方法。instance_eval和module_eval的主要区别在于定义的方法会放在哪里。如果你用String.instance_eval定义foo方法会得到String.foo,如果是用module_eval会得到String.new.foo。module_eval几乎总是适用;要像对待瘟疫一样避免使用eval。遵守这些简单的规则会对你有好处。9.实例变量的内省IntrospectoninstancevariablesRails使用了一个技巧来使controller中的实例变量也能用在view中,就是内省一个对象的实例变量。这会严重破坏封装,然而有时候确实非常顺手。可以很容易的通过instance_variables、instance_variable_get和instance_variable_set实现。要把所有实例变量从一个复制到另一个,可以这样:

Ruby代码§

from.instance_variables.eachdo|v|

to.instance_variable_setv,from.instance_variable_get(v)

end

[ruby]viewplain§copy§



from.instance_variables.eachdo|v|

to.instance_variable_setv,from.instance_variable_get(v)

end

10.从block创建Proc并公开CreateProcsfromblocksandsendthemaround把一个Proc实例化保存在变量中并公开的做法使得很多API容易使用。这是Markaby用来管理CSSclass定义的一种方法。很容易把block转换成Proc:

Ruby代码§

defcreate_proc(&p);p;end

create_procdo

puts"hello"

end#=>#

[ruby]viewplain§copy§



defcreate_proc(&p);p;end

create_procdo

puts"hello"

end#=>#

调用也很容易:

Ruby代码§

p.call(args)

[ruby]viewplain§copy§



p.call(args)

如果要用proc来定义方法,应该用lambda来创建,就可以用return和break:

Ruby代码§

p=lambda{puts"hoho";return1}

define_method(:a,&p)

[ruby]viewplain§copy§



p=lambda{puts"hoho";return1}

define_method(:a,&p)

如果有block的话method_missing会调用block:

Ruby代码§

defmethod_missing(name,args,&block)

block.call(args)ifblock_given?

end

thismethoddoesntexist("abc","cde")do|args|

pargs

end#=>["abc","cde"]

[ruby]viewplain§copy§



defmethod_missing(name,args,&block)

block.call(args)ifblock_given?

end

thismethoddoesntexist("abc","cde")do|args|

pargs

end#=>["abc","cde"]

11.用绑定(binding)来控制evalUsebindingtocontrolyourevaluations如果你确实需要用eval,你可以控制哪些变量是有效的。这时候要用kernel方法binding来获得所绑定的对象。例如:

Ruby代码§

defget_b;binding;end

foo=13

eval("putsfoo",get_b)#=>NameError:undefinedlocalvariableormethod`foo''formain:Object

[ruby]viewplain§copy§



defget_b;binding;end

foo=13

eval("putsfoo",get_b)#=>NameError:undefinedlocalvariableormethod`foo''formain:Object

ERb和Rails用这种技术来设置哪些实例变量是有效的。例如:

Ruby代码§

classHolder

defget_b;binding;end

end

h=Holder.new

h.instance_variable_set"@foo",25

eval("@foo",h.get_b)

[ruby]viewplain§copy§



classHolder

defget_b;binding;end

end

h=Holder.new

h.instance_variable_set"@foo",25

eval("@foo",h.get_b)





献花(0)
+1
(本文系非常尛貝首藏)